/* Availability calendar behaviours
   Author:		Andrew Hedges, andrew@bookabach.co.nz
   Date:		22/8/08
   Requires:	jquery.js
*/

if ('undefined' === typeof BOOKABACH) {
	BOOKABACH = {};
}

BOOKABACH.availabilityCalendar = (function () {
	// private members
	var
		// constants, of a sort
		NA					= 'N/A',
		PX					= 'px',
		TD					= 'TD',
		DIV					= 'DIV',
		GET					= 'GET',
		ODD					= 'odd',
		POA					= 'POA',
		SEQ					= 'seq',
		AUTO				= 'auto',
		DATE				= 'date',
		EVEN				= 'even',
		FAST				= 'fast',
		FREE				= 'free',
		PLUS				= '+',
		STEP				= 'step',
		TICK				= 'tick',
		INPUT				= 'input',
		POPUP				= '_popup',
		TODAY				= 'today',
		ACTION				= 'action',
		BOTTOM				= 'bottom',
		NORMAL				= 'normal',
		STRONG				= 'strong',
		TARGET				= 'target',
		NONTICK				= 'non-tick',
		SELECTED			= 'selected',
		MOUSEMOVE			= 'mousemove',
		UNDEFINED			= 'undefined',
		QSSTARTDATE			= 'startdate', // lowercased
		DATEINCREMENT		= 7,
		DATESELECTORTRACK	= 'date-selector-track',
		
		BAB		= 'BOOKABACH',
		GRID	= 'GRID',
		WIDGET	= 'WIDGET',
		
		STATUS				= 's',
		TARIFFID			= 't',
		HOLIDAYID			= 'h',
		TARIFFTYPE			= 'tt',
		
		FORUPTO				= 'for-up-to',
		MINNIGHTS			= 'min-nights',
		STARTDATE			= 'start-date',
		EXTRAADULT			= 'extra-adult',
		EXTRACHILD			= 'extra-child',
		BASEPERWEEK			= 'base-per-week',
		DISPLAYNAME			= 'display-name',
		BASEPERWEEKNIGHT	= 'base-per-week-night',
		BASEPERWEEKENDNIGHT	= 'base-per-weekend-night',
		
		LEFT				= 'left',
		RIGHT				= 'right',
		WIDTH				= 'width',
		CENTER				= 'center',
		TEXTALIGN			= 'text-align',
		PADDINGLEFT			= 'padding-left',
		PADDINGRIGHT		= 'padding-right',
		
		HOLIDAYTYPE			= 'holiday-type',
		HOLIDAYPUBLIC		= 'holiday-public',
		HOLIDAYSCHOOL		= 'holiday-school',
		HOLIDAYPUBLICPROV	= 'holiday-public-prov',
		
		HRHOLIDAYPUBLIC		= 'Public',
		HRHOLIDAYSCHOOL		= 'School',
		HRHOLIDAYPUBLICPROV	= 'Public-Prov',
		
		BOOKED				= 'booked',
		UNKNOWN				= 'unknown',
		ENQUIRYPENDING		= 'enquiry-pending',
		
		HRBOOKED			= 'Booked',
		HRUNKNOWN			= 'Availability unknown',
		HRAVAILABLE			= 'Available',
		HRENQUIRYPENDING	= 'Enquiry pending',
		HRPRICEONAP			= 'Price on application',
		HREXTRAADULT		= 'Extra adult',
		HREXTRACHILD		= 'Extra child',
		HRPERNIGHT			= '/night',
		
		CLICKTHETIMELINE	= 'Click the timeline to change the visible dates',
		
		DATESELECTORHANDLE	= 'date-selector-handle',
		
		// values
		_cache,
		_timer,
		_tmpls,
		_limits,
		_bottoms,
		_cellWidth,
		_firstDate,
		_elementCache,
		_selectedDates,
		_tipWidthFactor,
		_cellWidthFactor,
		_handleWidthFactor,
		
		// functions
		_adjustMonthHeaders,
		_animateCalendar,
		_attachBehaviours,
		_buildElements,
		_buildTooltipContent,
		_fixArrows,
		_formatDates,
		_getCalendarLeftFromHandleLeft,
		_getDateRangeFromHandle,
		_getElementOffsets,
		_getHandleLeftForDate,
		_getHandleLeftFromCalendarLeft,
		_getTooltipLeftFromHandleLeft,
		_getVisibleDatesFromCalendarLeft,
		_getX,
		_hasCents, 
		_hideTooltip,
		_humanReadableDate,
		_initProgressBar,
		_showHelpTip,
		_trackLeft
	;
	
	_bottoms = {
		track  : BOOKABACH.is.ie(6)? '170px' : BOOKABACH.is.ie()? '167px' : BOOKABACH.is.safari()? '156px' : '160px',
		handle : BOOKABACH.is.ie(6)? '170px' : BOOKABACH.is.ie()? '167px' : BOOKABACH.is.safari()? '156px' : '160px',
		cell   : BOOKABACH.is.ie(6)? '66px'  : BOOKABACH.is.ie()? '69px'  : BOOKABACH.is.safari()? '63px' : '66px'
	};
	
	// HTML snippet templates
	_tmpls = {
		trackDiv  : BOOKABACH.templates.make('<div class="#{classname}" date="#{date}" seq="#{seq}"#{holidayAttr}></div>'),
		thMonth   : BOOKABACH.templates.make('<th colspan="#{colspan}" class="#{oddeven}-month"><strong>#{month}</strong></th>'),
		th        : BOOKABACH.templates.make('<th#{holiday-title} class="#{holiday} #{oddeven}-day">#{dayofweek}<br /><strong>#{day}</strong><img src="/images/spacer.gif" width="32" height="1" alt="" /></th>'),
		td        : BOOKABACH.templates.make('<td seq="#{seq}" class="#{classname}">#{content}</td>'),
		thLast    : '<th> </th>',
		tdLast    : '<td class="unknown"><label><img src="/images/spacer.gif" alt="" style="width: 1px;height: 32px;" /></label></td>',
		checkbox  : BOOKABACH.templates.make('<label><input type="checkbox" name="#{name}" value="#{value}" /><br />#{label}</label>'),
		hrDate    : BOOKABACH.templates.make('#{day} #{month}, #{year}'),
		status    : BOOKABACH.templates.make('<h4>#{status}</h4>'),
		holiday   : BOOKABACH.templates.make('<div class="holiday"><em>#{display-name}</em></div>'),
		tooltip   : BOOKABACH.templates.make((function () {
			var html;
			html  = '<h4>#{status}</h4>';
			html += '#{price}';
			html += '#{for-up-to}';
			html += '#{extras}';
			html += '#{min-nights}';
			return html;
		})()),
		tipBooked : BOOKABACH.templates.make((function () {
			var html;
			html  = '<h4>#{status}</h4>';
			return html;
		})()),
		price : BOOKABACH.templates.make((function () {
			var html;
			html  = '<div class="price">';
			html += 	'#{per-night}/night#{per-week}';
			html += '</div>';
			return html;
		})()),
		forUpTo : BOOKABACH.templates.make((function () {
			var html;
			html  = '<div class="for-up-to">';
			html += 	'Pricing for up to #{for-up-to} people';
			html += '</div>';
			return html;
		})()),
		minNights : BOOKABACH.templates.make((function () {
			var html;
			html  = '<div class="min-nights">';
			html += 	'#{min-nights} night minimum stay';
			html += '</div>';
			return html;
		})()),
		extra : BOOKABACH.templates.make((function () {
			var html;
			html  = '<div class="extra-label">#{label}</div>';
			html += '<div class="extra-price">#{price}</div>';
			html += '<div class="dots">&nbsp;</div>';
			return html;
		})()),
		holidayTitle : BOOKABACH.templates.make((function () {
			var html;
			html  = ' title="#{display-name}"';
			return html;
		})())
	};
	
	// lower and upper bounds of the track handle and calendar (in pixels)
	_limits = {
		handle : {
			lower : -3,
			upper : undefined // gets defined once the DOM is built (should be on the order of 478)
		},
		calendar : {
			lower : undefined, // gets defined once the DOM is built (should be on the order of -19721)
			upper : 0
		}
	};
	
	// we'll store references to important elements here for quick lookups
	_elementCache = {};
	
	// stored as sequence numbers for easy comparison
	_selectedDates = [];
	
	/*	Keep month headers always visible
		@param int left CSS left property value
	*/
	_adjustMonthHeaders = function (left) {
		var dates, date, day, month, match, center, align;
		
		dates  = _getVisibleDatesFromCalendarLeft(left);
		day    = dates.start.getDate();
		month  = dates.start.getMonth();
		match  = BOOKABACH.utils.toTitleCase(BOOKABACH.dates.months[month]);
		center = Math.round(BOOKABACH.dates.getDaysInMonth(month) / 2);
		align  = day < 3 ? LEFT : day < center && day > 2 ? CENTER : RIGHT;
		
		$.each(_elementCache.calendarMonths, function () {
			var m;
			m = $(STRONG, this).html().split(' ')[0];
			if (m === match) {
				$(STRONG, this).css(TEXTALIGN, align);
			} else {
				$(STRONG, this).css(TEXTALIGN, LEFT);
			}
		});
	};
	
	/*	Animate the calendar and, optionally, move the track handle
		@param int left Ending left CSS position, expressed in pixels
		@param int step Number of days being stepped
		@param boolean skipHandle Whether to skip moving the track handle (optional, defaults to false)
	*/
	_animateCalendar = function (left, step, skipHandle) {
		var duration;
		skipHandle = skipHandle || false;
		left       = BOOKABACH.utils.limit(_limits.calendar.upper, _limits.calendar.lower, +left);
		duration   = Math.abs(50 * step);
		if (duration > 700) {
			duration = 700;
		}
		if (false === skipHandle) {
			_elementCache.handle.animate({
				left: _getHandleLeftFromCalendarLeft(left)
			}, duration);
		}
		_elementCache.calendar.animate({
			left: left + PX
		}, duration);
		setTimeout(function () {
			_adjustMonthHeaders(left);
		}, 0);
	};
	
	_attachBehaviours = function () {
		var showTip;
		
		showTip = function (html, element) {
			var css, seq, calLeft, visibleSeq, arrowPos, arrowCss, left, elementOffsets, calendarOffsets, elementHeight;
			
			// for now, bail if we're in grid mode
			if (GRID === _cache.mode) return;
			
			// add the content up-front so we have correct heights to work with later
			_elementCache.tooltipContent.html(html);
			
			css = {
				bottom : undefined, // used for single batch mode
//				top    : undefined, // used for grid mode, but assigned only when needed
				left   : undefined
			};
			
			if (TD === element.tagName) { // cell
				css.bottom = _bottoms.cell;
				
				seq        = +$(element).attr(SEQ);
				calLeft    = parseInt($(_elementCache.calendar).css(LEFT), 10);
				visibleSeq = seq - (Math.abs(calLeft) / _cellWidth);
				css.left   = visibleSeq * _cellWidth - _tipWidthFactor + _cellWidthFactor;
				
			} else { // DIV === element.tagName
				if (DATESELECTORHANDLE === element.id) { // handle
					css.bottom = _bottoms.handle;
					css.left   = _getTooltipLeftFromHandleLeft(_elementCache.handle.css(LEFT));
					
				} else { // track div
					css.bottom = _bottoms.track;
					
					seq        = +$(element).attr(SEQ);
					css.left   = seq + _trackLeft - _tipWidthFactor + PX;
				}
			}
			
			// futz with tooltip positions if we're in widget or grid modes
			if (WIDGET === _cache.mode) {
				// default to centered
				arrowPos = 'center top';
				
				if (DATESELECTORTRACK === element.parentNode.id) {
					_elementCache.tooltip.addClass(BOTTOM);
					
					left = parseInt(css.left, 10);
					
					// position arrow, if needed
					if (left < 2) {
						arrowPos = 62 - Math.abs(left - 2) + 'px top';
						css.left = '3px';
					} else if (left > 420) {
						arrowPos = 62 + (left - 420) + 'px top';
						css.left = '420px';
					}
					
				}
				else {
					_elementCache.tooltip.removeClass(BOTTOM);
					// position arrow, if needed
					switch (parseInt(css.left, 10)) {
						case -53:	arrowPos = '9px top';	css.left = '3px';	break;
						case -12:	arrowPos = '50px top';	css.left = '3px';	break;
						case 439:	arrowPos = '82px top';	css.left = '420px';	break;
						case 480:	arrowPos = '122px top';	css.left = '420px';	break;
					}
				}
				
				arrowCss = {
					'background-position' : arrowPos
				};
				
				_elementCache.tooltipArrow.css(arrowCss);
			}
			else if (GRID === _cache.mode) {
				elementOffsets  = BOOKABACH.utils.getCumulativeOffset(element);
				calendarOffsets = BOOKABACH.utils.getCumulativeOffset($('#availability-calendar').get(0));
				
				//	get the height of an invisible element:
				_elementCache.tooltip.css({
					visibility : 'hidden',
					display    : 'block'
				});
				elementHeight = BOOKABACH.ui.getTrueHeight($('#calendar-tooltip .content')[0]) + 12; // 12 is the height of the arrow
				_elementCache.tooltip.css({
					display    : 'none',
					visibility : 'visible'
				});
				
				css.top    = elementOffsets.y - calendarOffsets.y - elementHeight + PX;
				css.bottom = AUTO;
			}
			
			_elementCache.tooltip
				.css(css)
				.fadeIn(FAST)
			;
		};
		
		_trackLeft         = parseInt($('#date-selector-track').css(LEFT), 10);
		_tipWidthFactor    = Math.round(parseInt($(_elementCache.tooltip).width(), 10) / 2) - 1;
		_handleWidthFactor = Math.round(parseInt($(_elementCache.handle).width(), 10) / 2);
		_cellWidthFactor   = Math.round(_cellWidth / 2);
		
		// cell tooltips
		setTimeout(function () {
			$.each(_elementCache.calendarCells, function () {
				$(this)
					// MOUSEOVER
					.mouseover(function (evt) {
						var cell;
						cell = this;
						clearTimeout(_timer);
						_timer = setTimeout(function () {
							var seq, calLeft, visibleSeq, tipLeft, dates, firstLine, secondLine, html;
							seq = +$(cell).attr(SEQ);
							html       = _buildTooltipContent(seq, true);
							if (false !== html) showTip(html, cell);
						}, 10);
					})
					// MOUSEOUT
					.mouseout(function (evt) {
						_hideTooltip();
					})
				;
			});
		}, 0);
		
		// checkbox clicks
		setTimeout(function () {
			$('#calendar input')
					// CLICK
					.click(function (evt) {
					var seq, seqs, hiliteOne;
					// input > label > td.seq
					seq  = +$(this).parent().parent().attr(SEQ);
					seqs = {
						add    : [],
						remove : []
					};
					
					// hilite one cell at the expense of all others
					hiliteOne = function (seq) {
						seqs.add       = [seq];
						seqs.remove    = _selectedDates;
						_selectedDates = seqs.add;
					};
					
					// seq is not in selected range
					if ($.inArray(seq, _selectedDates) < 0) {
						// selected range is empty
						if (0 === _selectedDates.length) {
							hiliteOne(seq);
						
						// seq is immediately adjacent to selected range
						} else if (seq === _selectedDates[0] - 1 || seq === _selectedDates[_selectedDates.length - 1] + 1) {
							_selectedDates[_selectedDates.length] = seq;
							seqs.add[seqs.add.length]             = seq;
						
						// seq is not immediately adjacent, but shift key is down
						} else if (true === evt.shiftKey) {
							// keep already hilited cells and add this one and all between to the range
							if (seq < _selectedDates[0]) {
								for (var i = 0, len = _selectedDates[0] - seq; i < len; ++i) {
									// if there is an unbookable day (a cell without a checkbox) in the new range, hilite just the current click instead
									if (0 === $(INPUT, _elementCache.calendarCells[seq + i]).length) {
										hiliteOne(seq);
										break;
									}
									_selectedDates[_selectedDates.length] = seq + i;
									seqs.add[seqs.add.length]             = seq + i;
								}
							} else {
								for (var j = 0, len2 = seq - _selectedDates[0]; j < len2; ++j) {
									// if there is an unbookable day (a cell without a checkbox) in the new range, hilite just the current click instead
									if (0 === $(INPUT, _elementCache.calendarCells[seq - j]).length) {
										hiliteOne(seq);
										break;
									}
									_selectedDates[_selectedDates.length] = seq - j;
									seqs.add[seqs.add.length]             = seq - j;
								}
							}
						
						// seq is not immediately adjacent and shift key is not down
						} else {
							// unselect all other cells and hilite this one
							hiliteOne(seq);
						}
					// seq is in selected range
					} else {
						// seq is on one edge or the other
						if (seq === _selectedDates[0] || seq === _selectedDates[_selectedDates.length - 1]) {
							// remove it
							_selectedDates = $.grep(_selectedDates, function (s) {
								if (seq === s) {
									seqs.remove[seqs.remove.length] = seq;
									return false;
								} else {
									return true;
								}
							});
						// seq is not on an edge
						} else {
							// remove it and all with a greater date
							_selectedDates = $.grep(_selectedDates, function (s) {
								if (s < seq) {
									return true;
								} else {
									seqs.remove[seqs.remove.length] = s;
									return false;
								}
							});
						}
					}
					
					// act on the sequences (do the removes first to handle cases where there is an unbookable day in the range)
					if (seqs.remove.length > 0) {
						$.each(seqs.remove, function () {
							$(INPUT, _elementCache.calendarCells[this])[0].checked = false;
							$(_elementCache.calendarCells[this]).removeClass(SELECTED);
							$(_elementCache.trackDivs[this]).removeClass(SELECTED);
						});
					}
					
					if (seqs.add.length > 0) {
						$.each(seqs.add, function () {
							$(INPUT, _elementCache.calendarCells[this])[0].checked = true;
							$(_elementCache.calendarCells[this]).addClass(SELECTED);
							$(_elementCache.trackDivs[this]).addClass(SELECTED);
						});
					}
					
					_selectedDates = $.unique(_selectedDates);
					BOOKABACH.utils.numericalSort(_selectedDates);
				})
			;
		}, 0);
		
		// arrow clicks
		setTimeout(function () {
			var keysAndVals, startDate;
			
			if (GRID === _cache.mode) {
				keysAndVals = BOOKABACH.utils.getKeysAndValuesFromUrl(location.href, true);
				
				if (UNDEFINED === typeof keysAndVals[QSSTARTDATE]) {
					// use today +/- 7 for startDate
					startDate = BOOKABACH.dates.ymdToObj(_cache[TODAY]);     // ???
//					startDate = BOOKABACH.dates.ymdToObj(_cache[STARTDATE]); // ???
				} else {
					// use startDate from query string +/- 7
					startDate = BOOKABACH.dates.ymdToObj(keysAndVals[QSSTARTDATE]);
				}
				
				$('.prev a,.next a,#date-selector a').click(function () {
					var step, newStart, queryString;
					step          = -$(this).attr(STEP); // we need the opposite of this value, which was originally meant to determine the direction of an animation
					newStart      = BOOKABACH.dates.objToYmd(BOOKABACH.dates.addDays(startDate, step));
					keysAndVals[QSSTARTDATE] = newStart;
					queryString   = BOOKABACH.utils.makeQueryStringFromArray(keysAndVals);
					location.href = location.protocol + '//' + location.host + location.pathname + '?' + queryString;
					return false;
				});
			} else { // WIDGET === _cache.mode || BAB === _cache.mode
				$('.prev a,.next a,#date-selector a').click(function () {
					var step, left;
					step = +$(this).attr(STEP);
					left = parseInt(_elementCache.calendar.css(LEFT), 10) + (_cellWidth * step);
					_animateCalendar(left, step);
					_elementCache.tooltip.fadeOut(NORMAL);
					return false;
				});
			}
			
		}, 0);
		
		/*
		// jump-to select list (nav for widget version)
		setTimeout(function () {
			$('#jump-to').change(function () {
				var date, step, left;
				date = $(this).val();
				step = +BOOKABACH.dates.getDiff(new Date(), BOOKABACH.dates.ymdToObj(date)) + 1;
				left = -(_cellWidth * step);
				_animateCalendar(left, step);
			});
		}, 0);
		*/
		
		// TODO use event delegation to only attach 1 event handler instead of 435
		// slider
		setTimeout(function () {
			$('#date-selector-track div')
				// ONCLICK
				.click(function (evt) {
					var keysAndVals, date, step, target, left;
					if (DATESELECTORHANDLE !== evt.target.id) {
						date   = $(this).attr(DATE);
						if (GRID === _cache.mode) {
							keysAndVals              = BOOKABACH.utils.getKeysAndValuesFromUrl(location.href, true);
							keysAndVals[QSSTARTDATE] = date;
							queryString   = BOOKABACH.utils.makeQueryStringFromArray(keysAndVals);
							location.href = location.protocol + '//' + location.host + location.pathname + '?' + queryString;
							return false;
						} else {
							target = BOOKABACH.dates.addDays(BOOKABACH.dates.ymdToObj(date), -4);
							step   = BOOKABACH.dates.getDiff(_firstDate, target);
							left   = -(_cellWidth * step);
							_animateCalendar(left, step);
						}
					}
				})
				// ONMOUSEOVER
				.mouseover(function () {
					var div;
					div = this;
					clearTimeout(_timer);
					_timer = setTimeout(function () {
						var seq, left, html;
						seq  = +$(div).attr(SEQ);
						html = _buildTooltipContent(seq, false);
						if (false !== html) showTip(html, div);
					}, 10);
				})
				// ONMOUSEOUT
				.mouseout(function () {
					_hideTooltip();
				})
			;
		}, 0);
		
		// slider handle (nav for site version)
		setTimeout(function () {
			$('#date-selector-handle')
				// ONMOUSEOVER
				.mouseover(function () {
					var div;
					div = this;
					clearTimeout(_timer);
					_timer = setTimeout(function () {
						var dates, left, html;
						dates = _getDateRangeFromHandle();
						html  = _humanReadableDate(dates.start) + '<br />' + _humanReadableDate(dates.end);
						showTip(html, div);
					}, 10);
				})
				// ONMOUSEOUT
				.mouseout(function () {
					_hideTooltip();
				})
				// ONMOUSEDOWN
				.mousedown(function (evt) {
					clearTimeout(_timer);
					$(document)
						// ONMOUSEMOVE (window drag)
						.mousemove(function (evt) {
							clearTimeout(_timer);
							timer = setTimeout(function () {
								_showHelpTip(CLICKTHETIMELINE, _elementCache.tooltip.css(LEFT));
							}, 10);
							evt.preventDefault();
							evt.stopPropagation();
						})
						// ONMOUSEUP
						.mouseup(function (evt) {
							$(this).unbind(MOUSEMOVE);
						})
					;
				})
				/*
				// ONMOUSEDOWN
				.mousedown(function (evt) {
					var handle, limitX, offsets, startX;
					
					clearTimeout(timer);
					
					handle = this;
					
					// get these new each time in case user has resized window
					offsets = {
						track  : _getElementOffsets(_elementCache.track[0]),
						handle : _getElementOffsets(_elementCache.handle[0])
					};
					
					// number of pixels into the handle the user clicked
					startX = _getX(evt) - offsets.handle.x;
					
					$(document)
						// ONMOUSEMOVE
						.mousemove(function (evt) {
							var handleLeft, calendarLeft;
							
							// keep that tooltip open no matter what
							clearTimeout(timer);
							
							handleLeft = BOOKABACH.utils.limit(_limits.handle.upper, _limits.handle.lower, _getX(evt) - offsets.track.x - startX);
							$(handle).css({left : handleLeft + PX});
							
							calendarLeft = _getCalendarLeftFromHandleLeft(handleLeft);
							_animateCalendar(calendarLeft, 1, true);
							
							evt.preventDefault();
							evt.stopPropagation();
						})
						// ONMOUSEUP
						.mouseup(function () {
							$(this).unbind(MOUSEMOVE);
						})
					;
				})
				*/
			;
		}, 0);
	};
	
	_buildElements = function () {
		var
			// values
			checkbox, 
			date, 
			dates, 
			elementsAdded, 
			holiday, 
			holidayTitle, 
			idx, 
			seq, 
			showing, 
			thMonths, 
			ths, 
			tds, 
			track, 
			ticks, 
			trackElement, 
			// functions
			addElements, 
			addTrackDiv, 
			buildElements, 
			returnRate, 
			returnClassname, 
			returnHoliday
		;
		
		elementsAdded = false;
		
		addElements = function () {
			var keysAndVals, startDateQS, startDateCache, dateDiff, left;
			
			// workaround a bug in IE where this function gets called twice
			elementsAdded = true;
			
			// add the elements
			$('#date-selector-track')
				.append(track + '<div id="date-selector-handle"></div>')
				.css(WIDTH, seq + PX)
			;
			$('#calendar thead tr:first').append(thMonths);
			$('#calendar thead tr:last').append(ths);
			$('#calendar tbody tr:first').append(tds);
			
			// cache element references
			_elementCache.track          = $('#date-selector-track');
			_elementCache.trackDivs      = $('#date-selector-track div');
			_elementCache.calendar       = $('#calendar');
			_elementCache.calendarMonths = $('#calendar thead tr:first th');
			_elementCache.calendarCells  = $('#calendar tbody td');
			_elementCache.tooltip        = $('#calendar-tooltip');
			_elementCache.tooltipArrow   = $('.arrow', _elementCache.tooltip);
			_elementCache.tooltipContent = $('.content', _elementCache.tooltip);
			_elementCache.handle         = $('#date-selector-handle');
			
			// adjust animation limits
			_limits.handle.upper   = _elementCache.calendarCells.length - 17;
			_limits.calendar.lower = -((_elementCache.calendarCells.length * _cellWidth) - (showing * _cellWidth));
			
			// remove loading message
			$('#calendar-loading').remove();
			
			// show track and arrows
			$('#date-selector a,#date-selector-track').show();
			
			/*	We have to show the track before we attach the events because
				Mozilla returns a left value of 0px if the element is set to
				display: none
			*/
			
			// move track handle and calendar display depending on the start date
			keysAndVals = BOOKABACH.utils.getKeysAndValuesFromUrl(location.href, true);
			if (UNDEFINED !== typeof keysAndVals[QSSTARTDATE] && keysAndVals[QSSTARTDATE] !== _cache[TODAY]) {
				// get the date difference in days
				startDateQS    = BOOKABACH.dates.ymdToObj(keysAndVals[QSSTARTDATE]);
				startDateCache = BOOKABACH.dates.ymdToObj(_cache[TODAY]);
				dateDiff = BOOKABACH.dates.getDiff(startDateQS, startDateCache);
				// gate the handle position
				if (dateDiff > 439) {
					dateDiff = 439;
				}
				else if (dateDiff < -3) {
					dateDiff = -3;
				}
				
				if (GRID === _cache.mode) {
					// move the handle right 1px per day
					$('#date-selector-handle').css({
						left : dateDiff + PX
					});
				}
				else {
					// animate calendar to new start date
					left = parseInt(_elementCache.calendar.css(LEFT), 10) + (_cellWidth * -dateDiff);
					_animateCalendar(left, dateDiff);
				}
			}
			
			// prevent text selection on the calendar (when shift-clicking)
			BOOKABACH.ui.preventTextSelection('#calendar');
			_fixArrows();
			_attachBehaviours();
			
			_showHelpTip(CLICKTHETIMELINE, '160px');
		};
		
		addTrackDiv = function (dates) {
			var classname, holidayAttr;
			
			classname  = GRID === _cache.mode ? UNKNOWN + ' ' : _cache.dates[dates.ymd][STATUS] + ' ';
//			classname += 0 === dates.m % 2 ? ODD : EVEN; // opposite of what you might expect because javascript months are 0 - 11
			if (1 === dates.d) classname += ' ' + TICK;
			
			// TODO refactor holidays so they're not dependent on the number of days for let on the first bach
/* 			holidayAttr = 0 === _cache.dates[dates.ymd][HOLIDAYID] ? '' : ' holidayid="' + _cache.dates[dates.ymd][HOLIDAYID] + '"'; */
			holidayAttr = UNDEFINED === typeof _cache.dates[dates.ymd] || 0 === _cache.dates[dates.ymd][HOLIDAYID] ? '' : ' holidayid="' + _cache.dates[dates.ymd][HOLIDAYID] + '"';
			
			track += _tmpls.trackDiv.evaluate({
				classname   : classname,
				date        : dates.ymd,
				seq         : seq,
				holidayAttr : holidayAttr
			});
		};
		
		buildElements = function () {
			var startTime, doYield;
			
			startTime = new Date().getTime();
			
			while (UNDEFINED !== typeof _cache.dates[dates.ymd]) {
				++idx;
				
				addTrackDiv(dates);
				
				// add a new month header, if necessary
				if (1 === dates.d) {
					thMonths += _tmpls.thMonth.evaluate({
						colspan : BOOKABACH.dates.getDaysInMonth(dates.m, dates.y),
						oddeven : 0 === dates.m % 2 ? ODD : EVEN, // opposite of what you might expect because javascript months are 0 - 11
						month   : BOOKABACH.utils.toTitleCase(BOOKABACH.dates.months[dates.m]) + ' ' + dates.y
					});
				}
				
				holiday      = returnHoliday(dates.ymd);
				holidayTitle = '' === holiday ? '' : _tmpls.holidayTitle.evaluate({'display-name' : _cache.holidays[+_cache.dates[dates.ymd][HOLIDAYID]][DISPLAYNAME]});
				
				// add a new day header
				ths += _tmpls.th.evaluate({
					'oddeven'       : 0 === idx % 2 ? EVEN : ODD,
					'holiday'       : holiday,
					'holiday-title' : holidayTitle,
					'dayofweek'     : BOOKABACH.utils.toTitleCase(BOOKABACH.dates.daysOfTheWeek[dates.dow].substring(0, 3)),
					'day'           : dates.d
				});
				
				// add the cell for the date
				checkbox = BOOKED === _cache.dates[dates.ymd][STATUS] ? '' : _tmpls.checkbox.evaluate({name  : 'bookingDates', value : dates.ymd, label : returnRate(dates.ymd)});
				
				tds += _tmpls.td.evaluate({
					seq       : seq,
					classname : returnClassname(dates.ymd),
					content   : checkbox
				});
				
				// increment dates
				date  = BOOKABACH.dates.addDays(date, 1);
				dates = _formatDates(date);
				++seq;
				
				// if more than a few milliseconds have passed, give the UI a chance to respond
				if (100 < new Date().getTime() - startTime) {
					setTimeout(arguments.callee, 0);
/* 					break; // break only breaks out of the loop, leaving the next several lines to be executed */
					return; // return stops execution of the function, so the next several lines don't get executed
				}
			}
			
			ths   += _tmpls.thLast;
			tds   += _tmpls.tdLast;
			
			_elementCache.progress.increment(idx);
			
			if (false === elementsAdded && UNDEFINED === typeof _cache.dates[dates.ymd]) {
				addElements();
			}
		};
		
		returnRate = function (ymd) {
			var rate, tariffType, decimals;
			
			tariffType = 1 === _cache.dates[ymd][TARIFFTYPE] ? BASEPERWEEKENDNIGHT : BASEPERWEEKNIGHT;
			
			rate = UNDEFINED === typeof _cache.tariffs[_cache.dates[ymd][TARIFFID]] ? POA : _cache.tariffs[_cache.dates[ymd][TARIFFID]][tariffType];
			if (POA !== rate) {
				rate = BOOKABACH.utils.dollarFormat(Math.ceil(rate), 0);
				if (BOOKABACH.is.numeric(_cache.tariffs[_cache.dates[ymd][TARIFFID]][EXTRAADULT])) {
					rate += PLUS;
				}
			}
			return rate;
		};
		
		returnClassname = function (ymd) {
			if (BOOKED === _cache.dates[ymd][STATUS] || ENQUIRYPENDING === _cache.dates[ymd][STATUS] || UNKNOWN === _cache.dates[ymd][STATUS]) {
				return _cache.dates[ymd][STATUS];
			} else {
				return '';
			}
		};
		
		returnHoliday = function (ymd) {
			if (0 === +_cache.dates[dates.ymd][HOLIDAYID]) {
				return '';
			} else if (HRHOLIDAYPUBLIC === _cache.holidays[+_cache.dates[dates.ymd][HOLIDAYID]][HOLIDAYTYPE]) {
				return HOLIDAYPUBLIC;
			} else if (HRHOLIDAYPUBLICPROV === _cache.holidays[+_cache.dates[dates.ymd][HOLIDAYID]][HOLIDAYTYPE]) {
				return HOLIDAYPUBLICPROV;
			} else {
				return HOLIDAYSCHOOL;
			}
		};
		
		// add navigation arrows and track container
		$('#date-selector')
			.append(
				'<a class="prev" title="Show previous 7 days" step="7" href="#"><img id="date-selector-arrow-prev" src="/widgets/price-availability/images/arrow-left.png" alt="" /></a>' + 
				'<a class="next" title="Show next 7 days" step="-7" href="#"><img id="date-selector-arrow-next" src="/widgets/price-availability/images/arrow-right.png" alt="" /></a>' + 
				'<div id="date-selector-track"></div>'
			)
		;
		$('#date-selector a,#date-selector-track').hide();
		$('#availability-calendar').append('<div id="calendar-tooltip"><div class="content"></div><div class="arrow"></div></div>');
		
		track = ticks = tds = ths = thMonths = '';
		
		// use server date, not client date
		_firstDate = date = BOOKABACH.dates.ymdToObj(_cache[TODAY]);
		
		// save idx for later
		idx  = showing = $('#calendar thead:first tr:last th').length;
		seq  = 0;
		
		// start adding elements
		if (GRID === _cache.mode) {
			dates = _formatDates(date);
			for (var i = 0, len = 456; i < len; ++i) {
				addTrackDiv(dates);
				
				// add to the date for the days already showing
				date      = BOOKABACH.dates.addDays(date, 1);
				dates = _formatDates(date);

				++seq;
				
				_elementCache.progress.increment(i);
			}
			
			addElements();
		} else {
			dates = _formatDates(date);
			for (var i = 0, len = idx; i < len; ++i) {
				addTrackDiv(dates);
				
				// add to the date for the days already showing
				date  = BOOKABACH.dates.addDays(date, 1);
				dates = _formatDates(date);
				++seq;
			}
			
			buildElements();
		}
	};
	
	/*	Build the HTML for a tooltip
		@param int  seq     Sequence number
		@param bool showAll True to show full tariff information (optional, defaults to false)
		@return string
	*/
	_buildTooltipContent = function (seq, showAll) {
		var
			dates, 
			status, 
			tariffId, 
			holidayId, 
			tariffType, 
			isNumeric, 
			perNight, 
			perWeek, 
			decimals, 
			price, 
			forUpTo, 
			extraAdult, 
			extraChild, 
			extras, 
			minNights, 
			html,
			currency
		;
		
		// we don't have a sequence number for padded out dates
		if (!BOOKABACH.is.numeric(seq)) {
			return false;
		}
		
		showAll = showAll || false;
		dates   = _formatDates(BOOKABACH.dates.addDays(_firstDate, seq));
		status  = (function () {
			var hrStatus;
			switch (_cache.dates[dates.ymd][STATUS]) {
				case BOOKED:			hrStatus = HRBOOKED;			break;
				case ENQUIRYPENDING:	hrStatus = HRENQUIRYPENDING;	break;
				case UNKNOWN:			hrStatus = HRUNKNOWN;			break;
				default:				hrStatus = HRAVAILABLE;			break;
			}
			return hrStatus;
		})();
		
		currency = _cache.currency;
		// determine tariff & holiday IDs
		tariffId   = UNDEFINED === typeof _cache.dates[dates.ymd][TARIFFID]   ? -1 : _cache.dates[dates.ymd][TARIFFID];
		holidayId  = UNDEFINED === typeof _cache.dates[dates.ymd][HOLIDAYID]  ? -1 : _cache.dates[dates.ymd][HOLIDAYID];
		
		// determine the tariff type: per weekend night or per weeknight
		tariffType = 1 === +_cache.dates[dates.ymd][TARIFFTYPE] ? BASEPERWEEKENDNIGHT : BASEPERWEEKNIGHT;
		
		// is extra adult numeric?
		isNumeric = UNDEFINED !== typeof _cache.tariffs[tariffId] && UNDEFINED !== typeof _cache.tariffs[tariffId][EXTRAADULT] && NA !== _cache.tariffs[tariffId][EXTRAADULT];
		
		// per night price
		perNight = UNDEFINED === typeof _cache.tariffs[tariffId] ? POA : _cache.tariffs[tariffId][tariffType];
		if (BOOKABACH.is.numeric(perNight)) {
/*
			perWeek = UNDEFINED === typeof _cache.tariffs[tariffId] || POA === _cache.tariffs[tariffId][BASEPERWEEK] ? '' : _cache.tariffs[tariffId][BASEPERWEEK];
			if ('' !== perWeek) {
				decimals = _hasCents(perWeek) ? 2 : 0;
				if (BOOKABACH.is.numeric(perWeek)) perWeek  = ', ' + BOOKABACH.utils.dollarFormat(perWeek, decimals);
//				if (isNumeric) perWeek += PLUS;
				perWeek += '/week';
			}
*/
			perWeek  = '';
			decimals = _hasCents(perNight) ? 2 : 0;
			price = _tmpls.price.evaluate({
				'per-night' : BOOKABACH.utils.dollarFormat(perNight, decimals) + (isNumeric ? '+' : '') + ' ' + _cache.currency,
				'per-week'  : perWeek
			});
		} else {
			price = HRPRICEONAP;
		}
		
		// build a cell's tooltip
		if (showAll) {
			if (HRBOOKED === status) {
				html = _tmpls.tipBooked.evaluate({'status' : status});
			} else {
				if (HRPRICEONAP === price) {
					forUpTo = '';
					extras  = '';
				} else {
					forUpTo = NA === _cache.tariffs[tariffId][FORUPTO] || 0 === +_cache.tariffs[tariffId][FORUPTO] ? '' : _tmpls.forUpTo.evaluate({'for-up-to' : _cache.tariffs[tariffId][FORUPTO]});
					
					// extra adult
					if (UNDEFINED !== typeof _cache.tariffs[tariffId] && UNDEFINED !== typeof _cache.tariffs[tariffId][EXTRAADULT]) {
						decimals   = _hasCents(_cache.tariffs[tariffId][EXTRAADULT]) ? 2 : 0;
						extraAdult = isNumeric ? BOOKABACH.utils.dollarFormat(_cache.tariffs[tariffId][EXTRAADULT], decimals) + HRPERNIGHT : _cache.tariffs[tariffId][EXTRAADULT];
					}
					
					// extra child
					if (UNDEFINED !== typeof _cache.tariffs[tariffId] && UNDEFINED !== typeof _cache.tariffs[tariffId][EXTRACHILD]) {
						decimals   = _hasCents(_cache.tariffs[tariffId][EXTRACHILD]) ? 2 : 0;
						extraChild = NA === _cache.tariffs[tariffId][EXTRACHILD] ? _cache.tariffs[tariffId][EXTRACHILD] : BOOKABACH.utils.dollarFormat(_cache.tariffs[tariffId][EXTRACHILD], decimals);
						if ('$0' === extraChild) {
							extraChild = FREE;
						}
						else {
							extraChild += HRPERNIGHT;
						}
					}
					
					extras  = NA === extraAdult ? '' : _tmpls.extra.evaluate({
						'label' : HREXTRAADULT,
						'price' : extraAdult
					});
					
					extras += '' === extras ? '' : NA + HRPERNIGHT === extraChild ? '' : _tmpls.extra.evaluate({
						'label' : HREXTRACHILD,
						'price' : extraChild
					});
				}
				
				minNights = +_cache.tariffs[tariffId][MINNIGHTS] < 2 ? '' : _tmpls.minNights.evaluate({'min-nights' : _cache.tariffs[tariffId][MINNIGHTS]});
				
				html = _tmpls.tooltip.evaluate({
					'status'     : status,
/* 					'price'      : price, */
					'price'      : '',
					'for-up-to'  : forUpTo,
					'extras'     : extras,
					'min-nights' : minNights
				});
			}
		
		// build a timeline tooltip
		} else {
			html  = dates.hr;
			if (GRID !== _cache.mode) {
				html += _tmpls.status.evaluate({status : status});
				html += HRBOOKED === status? '' : price;
			}
			if (0 !== +_cache.dates[dates.ymd][HOLIDAYID] && UNDEFINED !== typeof _cache.holidays[_cache.dates[dates.ymd][HOLIDAYID]]) {
				html += _tmpls.holiday.evaluate({'display-name' : _cache.holidays[_cache.dates[dates.ymd][HOLIDAYID]][DISPLAYNAME]});
			}
		}
		
		return html;
	};
	
	_fixArrows = function () {
		if (BOOKABACH.is.ie(6) || BOOKABACH.is.ie(5)) {
			$.each($('.prev img,.next img'), function () {
				this.src = this.src.replace(/\.png/, '.gif');
			});
		}
	};
	
	_formatDates = function (date) {
		var dates;
		dates = {};
		dates.ymd = BOOKABACH.dates.objToYmd(date); // e.g., 2008-08-20
		dates.ym  = dates.ymd.substring(0, 7);      // e.g., 2008-08
		dates.y   = date.getFullYear();             // e.g., 2008
		dates.m   = date.getMonth();                // e.g., 0 == january
		dates.d   = date.getDate();                 // e.g., 20
		dates.dow = date.getDay();                  // e.g., 6 == saturday
		dates.hr  = _humanReadableDate(date);       // e.g., 9 Sep, 2008
		return dates;
	};
	
	_getCalendarLeftFromHandleLeft = function (handleLeft) {
		var calendarLeft;
		calendarLeft = (0 - handleLeft + 3) * _cellWidth;
		return calendarLeft;
	};
	
	_getDateRangeFromHandle = function () {
		var seq, dates;
		seq   = parseInt($('#date-selector-handle').css(LEFT), 10) + 3;
		dates = {};
		$.each(_elementCache.trackDivs, function () {
			if (seq === +$(this).attr(SEQ)) {
				dates.start = BOOKABACH.dates.ymdToObj($(this).attr(DATE));
				return false;
			}
		});
		dates.end = BOOKABACH.dates.addDays(dates.start, 13);
		return dates;
	};
	
	// based on http://www.webreference.com/programming/javascript/mk/column2/
	_getElementOffsets = function (element) {
		var offsets;
		
		if (UNDEFINED === typeof element) {
			throw 'PEBKAC: no element specified';
		}
		
		offsets = {
			x : element.offsetLeft,
			y : element.offsetTop
		};
		
		while (null !== element.offsetParent) {
			element    = element.offsetParent;
			offsets.x += element.offsetLeft;
			offsets.y += element.offsetTop;
		}
		
		return offsets;
	};
	
	_getHandleLeftForDate = function (date) {
		var ymd, left;
		ymd = BOOKABACH.dates.objToYmd(date);
		$.each(_elementCache.trackDivs, function () {
			if (ymd === +$(this).attr(DATE)) {
				left = +$(this).attr(SEQ) - 3 + PX;
				return false;
			}
		});
		return left;
	};
	
	_getHandleLeftFromCalendarLeft = function (calendarLeft) {
		var handleLeft;
		handleLeft = -3 + (Math.abs(parseInt(calendarLeft, 10)) / _cellWidth) + PX;
		return handleLeft;
	};
	
	_getTooltipLeftFromHandleLeft = function (handleLeft) {
		var tipLeft;
		tipLeft = parseInt(handleLeft, 10) + _handleWidthFactor + _trackLeft - _tipWidthFactor + PX;
		return tipLeft;
	};
	
	_getVisibleDatesFromCalendarLeft = function (left) {
		var dates;
		
		left = left || 0;
		
		dates       = {};
		dates.start = BOOKABACH.dates.addDays(_firstDate, Math.abs(left) / _cellWidth);
		dates.end   = BOOKABACH.dates.addDays(dates.start, 14);
		
		return dates;
	};
	
	// based on: http://www.webreference.com/programming/javascript/mk/column2/
	_getX = function (evt) {
		return UNDEFINED !== typeof evt.pageX ? evt.pageX : evt.clientX + document.body.scrollLeft - document.body.clientLeft;
	};
	
	/*	Return true or false depending on whether the value has a non-00 decimal (e.g., 1.23 returns true, 1.00 returns false)
		@param float value
		@return boolean
	*/
	_hasCents = function (value) {
		var string, cents;
		string = value.toString();
		if (string.indexOf('.') < 0) {
			return false;
		}
		else {
			cents = +string.split('.')[1];
			return (cents > 0);
		}
	};
	
	/*	Public method to hide the calendar tooltip (needed for bach tooltip calls)
	*/
	_hideTooltip = function () {
		clearTimeout(_timer);
		_timer = setTimeout(function () {
			if (UNDEFINED !== typeof _elementCache.tooltip) _elementCache.tooltip.fadeOut(NORMAL);
		}, 20);
	};
	// END: _hideTooltip
	
	// e.g., 21 Aug, 2008
	_humanReadableDate = function (date) {
		var dates;
		// handle invalid date objects for padding cells
		if (UNDEFINED !== typeof date.getFullYear && !BOOKABACH.is.numeric(date.getFullYear())) date = new Date();
		if ('string' === typeof date) {
			date = BOOKABACH.dates.ymdToObj(date);
		}
		dates = {
			d: date.getDate(),
			m: date.getMonth(),
			y: date.getFullYear()
		};
		return _tmpls.hrDate.evaluate({
			day   : dates.d,
			month : BOOKABACH.utils.toTitleCase(BOOKABACH.dates.months[dates.m]).substring(0, 3),
			year  : dates.y
		});
	};
	
	_initProgressBar = function () {
		var targetWidth, numSteps, step, idx, timer;
		
		targetWidth  = $('#calendar-loading').width();
		numSteps     = (function () {
			var num, key;
			num = 0;
			for (key in _cache.dates) {
				if ('function' !== typeof _cache.dates[key]) {
					++num;
				}
			}
			return num;
		})();
		step = targetWidth / numSteps;
		idx  = 0;
		
		_elementCache.progress           = $('#date-selector #calendar-loading .progress');
		_elementCache.progress.increment = function (idx) {
			var width;
			++idx;
			width = idx * step;
			if (width < targetWidth) {
				_elementCache.progress.width(idx * step);
			}
		};
	};
	
	_showHelpTip = function (text, left) {
		var css, elementOffsets, calendarOffsets, height;
		
		css = {
			bottom : _bottoms.handle,
			left   : left
		};
		
		$('#calendar-tooltip .content').html(text);
		
		if (WIDGET === _cache.mode) {
			$('#calendar-tooltip').addClass(BOTTOM);
		}
		
		if (GRID === _cache.mode) {
			trackOffsets    = BOOKABACH.utils.getCumulativeOffset($('#date-selector-track')[0]);
			calendarOffsets = BOOKABACH.utils.getCumulativeOffset($('#availability-calendar').get(0));
			height     = BOOKABACH.ui.getTrueHeight($('#calendar-tooltip')[0]);
			css.top    = trackOffsets.y - calendarOffsets.y - height + PX;
			css.bottom = AUTO;
		}
		
		$('#calendar-tooltip')
			.css(css)
			.fadeIn(FAST)
		;
	};
	
	// public members
	return {
		init: function (data) {
			var timers;
			
			timers = {};
			
			_cache = data;
			
			// are we in widget mode?
			if (WIDGET === _cache.mode) {
				// adjust bottoms
				_bottoms.track  = BOOKABACH.is.ie(6)? '145px' : BOOKABACH.is.ie()? '142px' : BOOKABACH.is.safari()? '137px' : '138px';
				_bottoms.handle = BOOKABACH.is.ie(6)? '145px' : BOOKABACH.is.ie()? '142px' : BOOKABACH.is.safari()? '137px' : '138px';
			}
			
			// init this value here because the DOM is ready
			_cellWidth = $('#calendar tbody td').length > 0 ? BOOKABACH.ui.getTrueWidth($('#calendar tbody td')[1]) : 0;
			
			// fetch availability info
			if (UNDEFINED === typeof _cache.dataSource) {
				throw 'PEBKAC: data source not specified';
			}
			else if (GRID !== _cache.mode) {
				$.ajax({
					url  : _cache.dataSource,
					type : GET,
					success : function (json) {
						// merge data into cache
						_cache.dates = (eval(json))[0];
						
						// initialise progress bar
						_initProgressBar();
						
						// proceed with build
						_buildElements();
					},
					error : function () {
						throw 'Server error fetching availability data.';
					}
				});
			}
			
			// activate bach tooltips here, rather than waiting for the ajax to return
			$('.bach-display')
				// MOUSEOVER
				.mouseover(function (evt) {
					var div;
					// hide the calendar tooltip, if it's showing
					_hideTooltip();
					div = this;
					clearTimeout(timers[div.id]);
					timers[div.id] = setTimeout(function () {
						$('.bach-tooltip', div).fadeIn(FAST);
					}, 10);
				})
				// MOUSEOUT
				.mouseout(function (evt) {
					var div;
					div = this;
					clearTimeout(timers[div.id]);
					timers[div.id] = setTimeout(function () {
						$('.bach-tooltip', div).fadeOut(NORMAL);
					}, 20);
				})
			;
			
			// trap form submissions
			$('form[name=booking-request]').submit(function (evt) {
				var trgt, href, opts;
				trgt = $(this).attr(TARGET);
				if (POPUP === trgt) {
					href = $(this).attr(ACTION) + '?' + $(this).serialize();
					opts = 'width=700,height=500,status=yes,toolbar=yes,resizable=yes,scrollbars';
					window.open(href, trgt, opts);
					// don't submit the form
					evt.preventDefault();
				}
			});
		}
	};
})();
