/*
	$Header:   //Nm-filestore/Archives/AdBooking/archives/webroot/adbooking/js/adbook.js-arc   1.47   Jul 06 2006 15:21:58   twoodwark  $

	Author: 		Toby Woodwark
	Date:			2005-08-08
	Name: 			adbook.js
	Description: 	Form validation for ad booking
	Globals:		main_form			- booking form id
					default_form_action	 - action of the form
					a_errors			- array of error objects
					application_adb_root = application.adb_root
					application_app_name= application.app_name
					application_adb_gfx = application.adb_gfx
					adbCallback 		- generic callback
					adbMetaMakeSelect,
					adbMetaModelSelect,
					adbMetaVariantSelect, 
					adbMetaStyleSelect	- references to elements in adb_book_copy.cfm (cars)
					o_cars 				- cars-related object
					a_table_ids			- calendar table ids
					monthIdx 			- index of current month in a_table_ids - adb_book_sched.cfm
					elSelectedTD		- reference to elements in adb_book_sched.cfm
					ad_id,
					package_id, 
					style_id,
					category_id			= form.ad_id etc.
					a_months			- month names
					a_logotable_ids		- logo table ids
					o_mapLogIdSrc		- maps ids to image sources
					el_logo				- td
					el_preview			- the HTML ad preview (copy step)
					el_wordcount		- span containing the wordcount: 'for x words' (copy step)
					el_pricequote		- span containing the price as a float (copy step)
					st_pricedetails 	- pricedetails info (copy step)
					b_use_img			= st_img_data.b_use_img (copy step)
					dt_min,dt_max		- limit dates for schedule selection, inclusive
					adb_upl_submit_img_default,
					adb_upl_submit_img_logo - images for submit button  
					request_additional_wordc_err_msg - '' or wordcount error message
	Requires:		addClassName.js
					getElementsByClassName.js
					arrayExtensions.js
					browserDetect.js
					email_valid.js
					cookieVal.js

	$Log:   //Nm-filestore/Archives/AdBooking/archives/webroot/adbooking/js/adbook.js-arc  $

   Rev 1.47   Jul 06 2006 15:21:58   twoodwark
safari fixes

   Rev 1.46   Jul 05 2006 17:54:10   twoodwark
attempted fixes for outrageous layout hax; safari

   Rev 1.45   Jun 19 2006 18:52:16   twoodwark
subpackages

   Rev 1.44   Jun 13 2006 16:08:48   twoodwark
no encode() thanks

   Rev 1.43   Jun 13 2006 14:41:06   twoodwark
new logo locations

   Rev 1.42   May 26 2006 13:37:26   twoodwark
reverted adbPuntTo signature

   Rev 1.41   May 26 2006 13:23:16   twoodwark
updated refere checking; added the query string for SiteCensus purposes

   Rev 1.40   May 24 2006 15:46:54   twoodwark
wording fix

   Rev 1.39   Mar 03 2006 10:53:32   twoodwark
(IE) kill unload for logo step, etc

   Rev 1.38   Feb 28 2006 14:26:12   twoodwark
comment

   Rev 1.37   Feb 21 2006 17:12:50   twoodwark
year = 1800 to 2007 (or whatever)

   Rev 1.36   Feb 20 2006 18:07:02   twoodwark
notallcaps validation

   Rev 1.35   Feb 17 2006 16:12:56   twoodwark
unbeforeunload rewrite

   Rev 1.34   Jan 17 2006 15:35:28   twoodwark
put price in statusbar

   Rev 1.33   Jan 16 2006 16:56:10   twoodwark
try to stop IE killing spaces *between* span elts

   Rev 1.32   Jan 12 2006 17:44:12   twoodwark
viewport checking

   Rev 1.31   Jan 11 2006 17:50:22   twoodwark
js hax - layout (removed rough guess)

   Rev 1.30   Jan 11 2006 12:06:08   twoodwark
new validation type (chars_range)

   Rev 1.29   Jan 09 2006 18:26:16   twoodwark
wordcount hack

   Rev 1.28   Jan 09 2006 11:41:06   twoodwark
error box cleverness

   Rev 1.27   Jan 03 2006 12:06:04   twoodwark
don't return false for onchange

   Rev 1.26   Dec 23 2005 11:48:34   twoodwark
more lenient name-validation


*/

encode=null;
if ("undefined"!=typeof encodeURIComponent) {encode= encodeURIComponent;}
else if ("undefined"!=typeof escape) {encode=escape;} //encoding urls
var main_form = 'adbMain';
var default_form_action = 'book.cfm';
/***************************************************************************
dev/util /unload
***************************************************************************/
function adbLog(sStr)
{	//->void
	try { debugAdb(sStr+'<br />');} catch(e) {}
}
var a_months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
function myDateString(dt) 
{	//->string
	return dt.getDate().toString() + ' ' + a_months[dt.getMonth()] + ' ' + dt.getFullYear();
}
function setBefUnload()
{	//->void
	adbLog('setBefUnload');
	window.onbeforeunload = adbBeforeUnload;
}
function killBefUnload()
{	//->void
	adbLog('killBefUnload');
	window.onbeforeunload=null;
}
function adbBeforeUnload ()
{	//->str
	adbLog('adbBeforeUnload');
	// NB. the msg string will be represented inside browser boilerplate.
	/* IE6/MozFF1.5:
		Are you sure you want to navigate away from this page?
		<msg>
		Press OK to continue, or Cancel to stay on the current page.
		[OK] [Cancel]
	*/
	var msg = 'To move between steps, use the \'previous\'/\'next\' buttons or numbered links.  If you press OK, you will leave the '+application_app_name+' booking process and lose your data.';
	if (window.event) { window.event.returnValue = msg; } // (IE)
	return msg;
}
/***************************************************************************
pseudo-sessions
***************************************************************************/
function adb_session_check_factory(url) 
{	//-> callable
	// generates a function which when called, sets a timeout redirecting user to url
	var ret = function () 
	{	adbLog('adb_session_check_factory lambda: url='+url);
	 	var ad_id = getCookieVal('adb_ad_id');
		var exptime = getCookieVal('adb_session_expiry');
		if (!exptime || !ad_id) {return;}
		// is the user using the same ad_id?
		var milliSecondsToWait = exptime - new Date().getTime(); 
		adbLog('adb_session_check_factory lambda: milliSecondsToWait='+ milliSecondsToWait);
		if (milliSecondsToWait >= 0)
		{ 	setTimeout('adbSessionEnd(\''+ad_id+'\',\''+url+'\')',milliSecondsToWait);
		}
	}
	return ret;
}
function adbSessionEnd(ad_id,url)
{	//->void
	//called when session dies.
	adbLog('adbSessionEnd: ad_id='+ad_id+',url='+url);
	var el_ad_id = document.getElementById('ad_id');
	adbLog('adbSessionEnd: el_ad_id.value='+el_ad_id.value);
	if (!el_ad_id||el_ad_id.value != ad_id) {return;}
	killBefUnload();
	location.href = url;
}
/***************************************************************************
layout
***************************************************************************/
function screenSize()
{	//->array
	var screen_x,screen_y;
	// how much screen?
	// http://www.quirksmode.org/viewport/compatibility.html
	if (self.innerHeight) // all except Explorer
	{	screen_x = self.innerWidth;
		screen_y = self.innerHeight;
	}
	else if (document.documentElement && document.documentElement.clientHeight) // Explorer 6 Strict Mode
	{ 	screen_x = document.documentElement.clientWidth;
		screen_y = document.documentElement.clientHeight;
	}
	else if (document.body) // other Explorers
	{ 	screen_x = document.body.clientWidth;
		screen_y = document.body.clientHeight;
	}
	return [screen_x,screen_y];
}
//add/remove classname on preview div depending on screen height (nonIE)
function adbCorrectPreviewLayout() //for onresize
{ 	//->void
	var el_p = document.getElementById('adbHTMLPreview')
	if (canUseFixedPos(el_p))
	{	addClassName(el_p,'previewOnLongPage');
	} else 
	{	delClassName(el_p,'previewOnLongPage');
	}
}
//IE only - store values
var ie_only_pre_offsetTop=-1;
var ie_only_pre_offsetLeft=-1;
var ie_only_scrheight=-1;
var ie_only_canuse=-1;
//add or remove style attributes (IE5-6)
function ie_only_adbCorrectPreviewLayout (force) //for onresize and onscroll
{ 	//->void
	// update the positioning of the preview div.
	// if parameter force is 'force', don't compare screenheight (hack to detect onscroll)
	var el = document.all.adbHTMLPreview;
	var scrHeight = screenSize()[1];
	if (!el) {return;}
	if (-1 == ie_only_pre_offsetTop)
	{	ie_only_pre_offsetLeft = findPosX(el);
		ie_only_pre_offsetTop = findPosY(el);
		ie_only_canuse = canUseFixedPos(el);
		ie_only_scrheight = scrHeight;
	}
	//if height of screen unchanged, use old val
	if (force=='force' || ie_only_scrheight != scrHeight)
	{	//(hack to detect onscroll)
		var b_canUse = canUseFixedPos(el);
	} else { var b_canUse = ie_only_canuse; }
	if (b_canUse)
	{	var top = ie_only_pre_offsetTop + 
			(document.documentElement.scrollTop)?document.documentElement.scrollTop:document.body.scrollTop;
		el.style.position="absolute";
		el.style.left = ie_only_pre_offsetLeft + "px";
		el.style.marginTop = top + "px";
	} else 
	{	el.style.position = "static";
		el.style.marginTop = "0px";
		el.style.left = "0px";
	}
	ie_only_scrheight = scrHeight;
	ie_only_canuse = b_canUse;
}
function canUseFixedPos(el_p) 
{	//->bool
	//return true if viewport height allows us to use position:fixed.
	
	// Complication: if the element has abolute positioning, then the offset
	// will already be relative to the viewport, so we need to subtract the
	// scrolltop of the document (/body).
	var sub = (el_p.style.position=="absolute")?((document.documentElement.scrollTop)?document.documentElement.scrollTop:document.body.scrollTop):0;
	//adbLog(screenSize()[1] +' >= '+findPosY(el_p)+' + '+el_p.offsetHeight+' + 30 - '+sub);
	return (screenSize()[1] >= (findPosY(el_p) + el_p.offsetHeight + 30 - sub)); //fudge = 30px	
}
/***************************************************************************
nav + feedback link
***************************************************************************/
function adbPuntTo(sStage,b_novalidate) 
{	//->void
	/* Move to different step with a POST. 
	Unless b_novalidate, validate first.	
	Delete the order id, if it exists in the form. (This is to stop the commit() processing code).
	*/
	if (sStage.toLowerCase() == 'pay') 
	{	qstring=''; //referer problem
	} else
	{	qstring='?stage='+sStage;
	}
	adbLog('adbPuntTo('+sStage+','+b_novalidate+') ; qstring='+qstring);
	killBefUnload();
	if (
		document.forms[main_form].elements['s']
		&& (b_novalidate || adb_val(document.forms[main_form])) 
		)
	{	// delete order id
		var e_oid = document.forms[main_form].elements['confirm_free_ad_orderid'];
		if (e_oid) { e_oid.value=''; }
		document.forms[main_form].elements['s'].value = sStage;
		document.forms[main_form].action = default_form_action + qstring;
		document.forms[main_form].submit();
	} else 
	{	setBefUnload();
		adbLog('adbPuntTo: b_novalidate ='+b_novalidate);
		adbLog('adbPuntTo: adb_val(document.forms[main_form]) ='+adb_val(document.forms[main_form]));
		adbLog('adbPuntTo: document.forms[main_form].elements[\'s\'] ='+document.forms[main_form].elements['s']);
	}
	return false;
}
function adbGotoFeedBack(url)
{	//->bool
	// append url variables to the link href; new window if available
	// return true to the onclick if can't open the window/location	
	if (!encode) {return true;}
	url += '?ad_id=';
	var e = document.forms[main_form].elements['ad_id'];
	if (e) {url += encode(e.value);}
	var a_contact = ['Name','Surname','Title','PhoneNo','EMail'];
	for (var i=0,l=a_contact.length;i<l;i++)
	{	e = document.forms[main_form].elements['contact_'+a_contact[i]];
		if (e) {url += '&' + a_contact[i] + '=' + encode(e.value);}
	}
	try 
	{	if (window.open(url)) {return false;}
	} catch (e)
	{	return true;
	}
	location.href=url;
	return false;
}
/***************************************************************************
onsubmit function
***************************************************************************/
var a_errors = [];
function adb_val (fForm) 
{	//->bool
	// onsubmit() of the main form
	var b_valid=true;
	for (var i=0,l=fForm.elements.length;i<l;i++) 
	{ 	if (fForm.elements[i].val) 
		{	b_valid = (fForm.elements[i].val() && b_valid);
		}
	}
	if (b_valid) {killBefUnload();}
	if (!b_valid && !b_safari) 
	{	//take user to one of the error messages.
		//NB. don't use safari's location.hash support .
		if (document.getElementById('adbErrorBox')) {
			//go to top error box
			location.hash='adbErrorBox';
		} else if (typeof getElementsByClassName != 'undefined') 
		{	//get list of error divs and pick first one (source order)
			var errs = getElementsByClassName(fForm,'adbErr');
			if (errs.length && errs[0].id) {location.hash=errs[0].id};
		}
	}
	return b_valid;
}
/***************************************************************************
validation objects
***************************************************************************/
function clearError (eElement,sString)
{	//-> bool. 
	// Remove all errors for an element, or (if string nonempty) just those
	// matching the string
	// Return true if no error remains
	if (eElement && eElement.error)
	{	if (!sString || eElement.error.sString !== sString)
		{	eElement.error.destroy();
		} else
		{	return false;
		}
	}
	return true;
}
function validationError (eElement, sString, bDispTopError)
{	/*-> new error object.
	The error HTML is a <div> element.
	It lives in either (a) a <td> to the right of the offending element
	or (b) within the div#adbErrorBox (where it will look like an <li> )
	*/
	//adbLog('validationError: there are currently '+a_errors.length+' errors, adding one');
	var el_TD_target,el_errorbox,msg;
	if (!eElement || !eElement.id) 
	{	adbLog('validationError: no element found! sString='+sString);
	} else
	{ 	adbLog('validationError: eElement.id='+eElement.id+',sString='+sString);
		if (!clearError(eElement,sString))
		{ 	adbLog('validationError: clearError('+eElement+','+sString+') was false');
			return; //already got same error there
		}
	}
	adbLog('validationError: bDispTopError='+bDispTopError);
	// attach references
	this.sString = sString;
	if (eElement) 
	{	eElement.error = this;
		this.eElement = eElement;
	} else 
	{	//must be unique across errors.
		//so let's look for errors with same string
		if (a_errors.contains(this))
		{ 	adbLog('validationError: already a generic error for sString='+sString);
			return;
		}
	}
	this.bDispTopError = !!bDispTopError;// top error notification
	a_errors[a_errors.length] = this;
	this.notify();
	// make error string HTML
	msg = document.createElement('div');
	if (eElement) {msg.id = 'error_msg_'+eElement.id;}
	msg.appendChild(this.getErrImg());
	msg.appendChild(document.createTextNode(' '+sString)); //nbsp is \u00a0 if you want
	addClassName(msg,'adbErr');
	if (b_safari) {addClassName(msg,'adbInfo_safarifix');}
	// locate TD to put error in, if we can do that
	if (eElement) {var el_TD_target = document.getElementById('adbInfoTD_'+eElement.id);}
	if (el_TD_target) 
	{ 	// create HTML inside a TD somewhere
		for (var i=0,l=el_TD_target.childNodes.length;i<l;i++)
		{	addClassName(el_TD_target.childNodes[i],'adbNotShown');
		}
		el_TD_target.appendChild(msg);
	}	else
	{	// create HTML inside the top box
		adbLog('validationError: no td element');
		el_errorbox = document.getElementById('adbErrorBox');
		if (el_errorbox) { el_errorbox.appendChild(msg);}
		else {adbLog('validationError: failed, no el_errorbox');}
	}
	// attach references
	this.msg = msg;
	return this;
}
validationError.prototype.getErrImg = function () 
{	//-->img 
	var img = document.createElement('img');
	img.src = application_adb_gfx + 'i_error.gif';
	img.alt = '';
	return img;
}
validationError.prototype.notify = function () 
{	//-> void
	// Create the generic error warning info box, if needed.
	// We look for a node with an id of 'adbErrorBefore' and put the box before that
	// in the dom tree.
	adbLog('validationError.prototype.notify: this.bDispTopError='+this.bDispTopError);
	var el_errbefore = document.getElementById('adbErrorBefore');
	if (!el_errbefore)
	{	adbLog('validationError.prototype.notify: no el_errbefore');
		return;
	}
	var el_errorbox = document.getElementById('adbErrorBox');
	if ( a_errors.length && this.bDispTopError )
	{	if (!el_errorbox)
		{	//create error box
			var el_errorbox = document.createElement('div');
			el_errorbox.id = 'adbErrorBox';
			el_errorbox.appendChild(this.getErrImg());
			el_errbefore.parentNode.insertBefore(el_errorbox,el_errbefore);
		} 
	} else 
	{	if (el_errorbox) {el_errorbox.parentNode.removeChild(el_errorbox);}
	}

}
validationError.prototype.toString = function () 
{	//-> string
 	// This func is used in arrayExtensions
	if (this.eElement) 
	{	return this.eElement.id;
	} else if (this.sString) 
	{	return this.sString;
	} else
	{	return 'validationError';
	}
}
validationError.prototype.destroy = function () 
{	//->void
	adbLog('validationError.prototype.destroy: this='+this.toString());
	if (this.msg.parentNode)
	{ 	for (var i=0,l=this.msg.parentNode.childNodes.length;i<l;i++)
		{	delClassName(this.msg.parentNode.childNodes[i],'adbNotShown');
		}
		this.msg.parentNode.removeChild(this.msg);
	}
	try
	{	try 
		{ 	delete this.eElement.error;
			delete this.eElement;
			delete this.sString;
			delete this.msg;
		} catch (e) 
		{	//IE - set to null
			this.eElement.error = null;
			this.eElement = null;
			this.sString = null;
			this.msg = null;	
		}
	} catch (e) {} 
	a_errors = a_errors.removed(this);
	this.notify();
}
/***************************************************************************
validation functions
***************************************************************************/
function zapErrors()
{	//->void
	// zap all errors
	if (a_errors)
	{ 	while (a_errors.length) 
		{	a_errors[0].destroy();
		}
	}
}
function val_postcode(eElement,sErrMsg,bDispTopError) 
{	//->bool
	//just see if it's uppercase
	return val_upper(eElement,sErrMsg,trim(eElement.value),bDispTopError);
}
function val_notallcaps(eElement,sErrMsg,bDispTopError) 
{	//->bool
	//fail if it's nonempty and all uppercase
	if (eElement.value && trim(eElement.value) && eElement.value == eElement.value.toUpperCase())
	{	new validationError(eElement,sErrMsg,bDispTopError);
		return false;
	} else 
	{	clearError(eElement);
		return true;
	}
}
function val_radio(eElement,sErrMsg,sEltId,bDispTopError) 
{ 	//->bool
	// One option must be checked
	var val_elt = (sEltId)?document.getElementById(sEltId):eElement;
	var radioname = eElement.name;
	adbLog('val_radio: radioname='+radioname);
	for (var i=0,l=eElement.form.elements.length;i<l;i++)
	{ 	if (eElement.form.elements[i].name == radioname && eElement.form.elements[i].checked)
		{	clearError(eElement);
			adbLog('val_radio: got='+eElement.form.elements[i].id);
			return true;
		}
	}
	new validationError(val_elt,sErrMsg,bDispTopError);
	return false;
}
function val_checked(eElement,sErrMsg,bDispTopError) 
{ 	//->bool
	//checked
	if (!eElement.checked)
	{	new validationError(eElement,sErrMsg,bDispTopError);
		return false;
	} else 
	{	clearError(eElement);
		return true;
	}
}
function val_nonnegative(eElement,sErrMsg,bDispTopError) 
{ 	//->bool
	//non negative value
	if (eElement.value < 0)
	{	new validationError(eElement,sErrMsg,bDispTopError);
		return false;
	} else 
	{	clearError(eElement);
		return true;
	}
}
function val_forsale_wanted(eElement,sErrMsg,bDispTopError) 
{ 	//->bool
	//elt must have contain "For sale" or "Wanted"
	if (!eElement.value.toLowerCase().match(/\b(for[\s]+sale|wanted)\b/))
	{	new validationError(eElement,sErrMsg,bDispTopError);
		return false;
	} else 
	{	clearError(eElement);
		return true;
	}
}
function val_nonempty(eElement,sErrMsg,bDispTopError) 
{ 	//->bool
	//elt must have some kind of value, after trimming
	if (eElement.value.replace(/[\s]+/g,'') == '')
	{	new validationError(eElement,sErrMsg,bDispTopError);
		return false;
	} else 
	{	clearError(eElement);
		return true;
	}
}
function val_year(eElement,sErrMsg,value,bDispTopError)
{ 	//->bool
	//elt must have 4-char numeric value or be empty
	if (!value) { 	var value = trim(eElement.value); }
	if (value=='')
	{	clearError(eElement);
		return true;
	} else if (/^[1-9][\d]{3,3}$/.test(value))
	{	var y=parseInt(value,10);
		if (y >= 1800 && y <= (new Date().getFullYear()+1)) 
		{	clearError(eElement);
			return true;
		}
	}
	new validationError(eElement,sErrMsg,bDispTopError);
	return false;
}
function val_numeric(eElement,sErrMsg,value,bDispTopError)
{ 	//->bool
	//elt must have numeric value or be empty
	if (!value)
	{ 	var value = trim(eElement.value);
	}
	if (value=='' || /^([1-9][\d]{0,2}(,\d\d\d)*|[\d]+)$/.test(value)) 
	{	clearError(eElement);
		return true;
	}
	new validationError(eElement,sErrMsg,bDispTopError);
	return false;
}
function val_range(count,min,max,eElement,sErrMsg,bDispTopError)
{ 	//->bool
	//adbLog('val_range: count='+count+',min='+min+',max='+max+',eElement='+eElement+',sErrMsg='+sErrMsg+',bDispTopError='+bDispTopError);
	if ((!isNaN(min) && count<min )||( !isNaN(max) && count>max))
	{	new validationError(eElement,sErrMsg,bDispTopError);
		return false;
	}
	clearError(eElement);
	return true;
}
function val_range_factory(type,min,max)
{	//->callable
	//return a validation function taking args (eElement,sErrMsg,[value],[bDispTopError])
	var ret = function (eElement,sErrMsg,value,bDispTopError)
	{ 	//->bool
		var count,b;
		if (!value)
		{ 	var value = trim(eElement.value);
		}
		// get the right count
		switch (type) {
			case 'numeric_range': 
				// counting a number; 
				// destroy any leading non-digits or commas, then get float
				value = value.replace(/^[\D]+|,/g,'');
				count = parseFloat(value);
				// hackily turn trailing 'k' into '000'
				if (!isNaN(count) && /^[\.\d]+k(\b|$)/i.test(value))
				{	count *= 1000;
				}
				break;
			case 'chars_range':
			case 'chars_range_numeric':
				//counting chars
				count = value.length;
				break
			default:
				//words
			 	count = count_words(value);
		}
		b = val_range(count,min,max,eElement,sErrMsg,bDispTopError);
		//adbLog('val_range_factory lambda: val_range('+count+','+min+','+max+','+eElement+','+sErrMsg+','+bDispTopError+')='+b);
		if (type=='chars_range_numeric')
		{	// need to validate that it is numeric as well
			b = (b && val_numeric(eElement,sErrMsg,value,bDispTopError));
		}
		return b;
	}
	return ret;
}
function val_alpha(eElement,sErrMsg,value,bDispTopError)
{ 	//->bool
	//elt must have [A-Za-z ] value or be empty
	if ( typeof bDispTopError == 'undefined' ) bDispTopError = false;
	if (!value)
	{ 	var value = trim(eElement.value);
	}
	if (value=='' || /^[A-Za-z\s\-\'\.]+$/.test(value)) 
	{	clearError(eElement);
		return true;
	}
	new validationError(eElement,sErrMsg,bDispTopError);
	return false;
}
function val_email(eElement,sErrMsg,bDispTopError)
{	//->bool
	if (eElement && validate_addr(eElement.value)) 
	{	clearError(eElement);
		return true;
	}
	new validationError(eElement,sErrMsg,bDispTopError);
	return false;
}
function val_matched(eElement,eElement2,sErrMsg,bDispTopError)
{	//->bool
	//fail if 2 elements have differing values
	if (eElement && eElement2 && eElement.value == eElement2.value) 
	{	clearError(eElement);
		return true;
	}
	new validationError(eElement,sErrMsg,bDispTopError);
	return false;
}
function val_phoneno(eElement,sErrMsg,bDispTopError)
{ 	//->bool
	//elt must have numeric value (incl + and ())
	return val_numeric(eElement,sErrMsg,eElement.value.replace(/[\s\+\(\)]+/g,''),bDispTopError);
}
function val_currency(eElement,sErrMsg,bDispTopError)
{ 	//->bool
	//zap any leading £ signs
	while (eElement.value.charCodeAt(0)==163) {eElement.value = eElement.value.substr(1);}
	//elt must have numeric value (incl .99 and k)
	var value = trim(eElement.value).replace(/(\.\d\d[\s]*$|k[\s]*$)/gi,'');
	if (/^([1-9][\d]{0,2}(,\d\d\d)*|[\d]+)$/.test(value))
	{	clearError(eElement);
		return true;
	}
	new validationError(eElement,sErrMsg,bDispTopError);
	return false;
}
function val_title_select(eSelect,choices,package_id,groupnum) 
{ 	//->bool
	//user must choose exact number
	if (package_id == mypackage_id) 
	{ 	var selectnum = 0;
		for (var i=0,l=eSelect.options.length;i<l;i++) 
		{ 	if (eSelect.options[i].selected) 
			{	selectnum++;
			}
		}
		if (selectnum != choices) 
		{	var sErrMsg = 'Please select '
			+((choices==1)?'a title ':'exactly '+choices+' titles ');
			if ( typeof groupnum != 'undefined' ) {sErrMsg += 'from group '+groupnum;}
			sErrMsg += '.';
			new validationError(eSelect,sErrMsg,true);
			return false;
		} else 
		{	clearError(eSelect);
			return true;
		}
	}
	return true;
}
function val_sched() 
{ 	//->bool
	//user must choose a date within range
	adbLog('val_sched: dt_min='+dt_min+', dt_max='+dt_max);
	var elDate = document.getElementById('sched_select_date');
	try 
	{ 	var sDt = elDate.value.split('-');
		adbLog('val_sched: sDt = '+sDt);
		var time_selected = new Date(sDt[0],sDt[1]-1,sDt[2]).getTime();
		adbLog('val_sched: time_selected = '+time_selected);
		if (isNaN(time_selected) || typeof time_selected !== 'number' || time_selected < 0) {throw new Error('wtf');}
		if (time_selected > dt_max.getTime() || time_selected < dt_min.getTime())
		{ 	new validationError(elDate,'Please select a date between '+myDateString(dt_min)+' and '+myDateString(dt_max)+'.',true);
			return false;
		}
	}
	catch(e) 
	{ 	new validationError(elDate,'Please select a date.',true);
		adbLog('val_sched: err='+e);
		return false;
	}
	clearError(elDate);
	return true;
}
function val_upper(eElement,sErrMsg,sValue,bDispTopError)
{ 	//->bool
	//the upper case of the name must be the same as the value
	if ( typeof bDispTopError == 'undefined' ) bDispTopError = false;
    if (!sValue)
	{		var sValue = trim(eElement.value);
	}
	if (sValue == sValue.toUpperCase() )
	{	clearError(eElement);
		return true;
	}
	new validationError(eElement,sErrMsg,bDispTopError);
	return false;	
}
function onchange_from_val() 
{	//-> true
	// this function calls 'this.val' and then returns true.
	// for use on elements which should be validated onchange
	if (this.val) {this.val();}
	return true;
}
/***************************************************************************
wordcount+util
***************************************************************************/
function count_words(str)
{	//->int
	//adbLog('count_words: str='+str);
	// IE seems to have random entities in its innerHTML.
	// remove nbsp because it shouldn't affect wordcount.
	var str = str.replace(/\&(nbsp|#160);/gi,'');
	if (str.match(/^[\s]+$/)) {return 0;}
	return trim(str).split(/[\s]+/g).length;
}
function trim(val)
{	return val.replace(/[\s]+$|^[\s]+/g,'');
}
function simplePluralize(word,val)
{	//->string
	return (val==1)?word:word+'s';
}
function moneyFormat(number) 
{	var pounds = Math.floor(number).toString();
	var pence = Math.floor(number*100)%100;
	return pounds + '.' + (pence<10?'0'+pence:pence);
}
function sanitizeInput(str)
{	return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
}
/***************************************************************************
package + titles
***************************************************************************/
function set_package() 
{	//->void
	// user has chosen a package
	zapErrors();
	// make title selection visible
	mypackage_id = getSelectedPackageId ();
	var a_divs = document.getElementById(main_form).getElementsByTagName('div');
	// show/hide divs
	for (var i=0,l=a_divs.length;i<l;i++) 
	{ 	if (a_divs[i].id) 
		{ 	var a_matches = /title_sel_([\d]+)/.exec(a_divs[i].id);
			if (a_matches) 
			{ 	//hide the noscript-hint 
				var el_noscr_hint = document.getElementById('title_sel_noscr_hint_'+a_matches[1]);
				if (el_noscr_hint)
				{	el_noscr_hint.style.display='none';
					addClassName(el_noscr_hint,'adbNotShown');
				}
				if (a_matches[1] == mypackage_id) 
				{	delClassName(a_divs[i],'adbNotShown');
				} else 
				{	addClassName(a_divs[i],'adbNotShown');
				}
			}
		}
	}
}

function getSelectedPackageId ()
{	//->int
	adbLog('getSelectedPackageId');
	var eElt = document.getElementById('pack_super');
	for (var i=0,l=a_package_grp.length;i<l;i++)
	{ 	var e_subsel = document.getElementById('package_id_'+a_package_grp[i]);
		if (eElt.value==a_package_grp[i])
		{	if (e_subsel.type=='hidden')
			{	return e_subsel.value;
			}
			else 
			{	for (var j=0,k=e_subsel.options.length;j<k;j++)
				{	if (e_subsel.options[j].selected) {return e_subsel.options[j].value;}
				}
			}
		}
		 
	}
	return -1;
}

function packChange (eElt)
{	//->void
	adbLog('packChange: eElt.value='+eElt.value);
	for (var i=0,l=a_package_grp.length;i<l;i++)
	{ 	var e_subsel = document.getElementById('package_id_'+a_package_grp[i]);
		adbLog('packChange: e_subsel.type='+e_subsel.type);
		if (!(e_subsel.type=='hidden'))
		{ 	if (eElt.value==a_package_grp[i])
			{	delClassName(e_subsel,'adbNotShown');
				e_subsel.val = function() {return val_nonempty(this,'Please choose a package.',true)};
			} else 
			{	e_subsel.selectedIndex=0;
				addClassName(e_subsel,'adbNotShown');
				e_subsel.val = function() {return true;};
			}
		} 
		e_subsel.disabled = !(eElt.value==a_package_grp[i]);
	}
}

function classChange (eElt)
{	//->void
	adbLog('classChange: eElt.value='+eElt.value);
	for (var i=0,l=a_superclass.length;i<l;i++)
	{ 	var e_subsel = document.getElementById('classification_id_'+a_superclass[i]);
		adbLog('classChange: e_subsel.type='+e_subsel.type);
		if (!(e_subsel.type=='hidden'))
		{ 	if (eElt.value==a_superclass[i])
			{	delClassName(e_subsel,'adbNotShown');
				e_subsel.val = function() {return val_nonempty(this,'Please choose a classification.',true)};
			} else 
			{	e_subsel.selectedIndex=0;
				addClassName(e_subsel,'adbNotShown');
				e_subsel.val = function() {return true;};
			}
		} 
		e_subsel.disabled = !(eElt.value==a_superclass[i]);
	}
}
/***************************************************************************
cars
***************************************************************************/
var adbMetaMakeSelect,adbMetaModelSelect, adbMetaVariantSelect, adbMetaStyleSelect, o_cars;
function adbMetaMakeChange() 
{	//->void
	//user has changed model selection
	if (adbMetaModelSelect && adbMetaMakeSelect && adbMetaMakeSelect.value && adbMetaMakeSelect.value != -1)
	{ 	adbSetCarOpts(adbMetaModelSelect,o_cars[adbMetaMakeSelect.value],'MODEL',true);
	}
}
function adbMetaModelChange() 
{	//->void
	var themodel;
	if (adbMetaMakeSelect && adbMetaMakeSelect.value && adbMetaMakeSelect.value != -1
	&& adbMetaModelSelect && adbMetaModelSelect.value && adbMetaModelSelect.value != -1)
	{ 	for (var i=0,l=o_cars[adbMetaMakeSelect.value].length;i<l;i++) 
		{	if (o_cars[adbMetaMakeSelect.value][i].MODEL == adbMetaModelSelect.value) 
			{ 	themodel=o_cars[adbMetaMakeSelect.value][i];
				break;
			}
		}
		if (themodel)
		{	if (adbMetaVariantSelect) {adbSetCarOpts(adbMetaVariantSelect,themodel.VARIANTS);}
			if (adbMetaStyleSelect) {adbSetCarOpts(adbMetaStyleSelect,themodel.STYLES,null,true);}
		}
	}
}
function adbSetCarOpts(eSelect,aValues,key,b_setdefault_on_one)
{	//->void
	//repopulate a <select> element
	
	//eSelect = the select element to repopulate
	//aValues = array of vlaues, or objects containing values
	//[key]	  = if defined, object key to use in value lookup
	//[b_setdefault_on_one] = if true, and only 1 (true) option available in
	//						eventual option list, select it
	
	adbLog('adbSetCarOpt: eSelect.id='+eSelect.id+',aValues='+aValues.join(',')+',key='+key+',b_setdefault_on_one='+b_setdefault_on_one);
	var el_opt; //the option elements
	var val,oldval,newselectedindex; //store+restore value of the <select>
	oldval = eSelect.value;
	adbLog('adbSetCarOpt: oldval='+oldval);
	newselectedindex=0;
	eSelect.options.length = 1; //zap it.  First <option> is always value -1, string set below
	for (var i=0,l=aValues.length;i<l;i++)
	{	el_opt = document.createElement('option');
		val = (key)?aValues[i][key]:aValues[i];
		el_opt.appendChild(document.createTextNode(val));
		el_opt.value = val;
		eSelect.appendChild(el_opt);
		if (oldval.toString()==val.toString()) 
		{	newselectedindex=i+1;
			adbLog('adbSetCarOpt: selected value='+val);
		}
	}
	eSelect.options[0].innerHTML = (eSelect.options.length > 1)?' -- select one -- ':' -- none -- ';
	eSelect.disabled = (eSelect.options.length > 1)?false:true;
	//set the index.
	//if b_setdefault_on_one was passed and we have only 1 choice - choose it.
	//this makes things a bit more usable.
	if (b_setdefault_on_one && eSelect.options.length==2) 
	{	eSelect.selectedIndex=1;
		//we have forced a possible change in value, so revalidate that <select>
		if (eSelect.val) {eSelect.val();}
	} else 
	{	try
		{ 	//belt and braces approach to try and work around mysterious IE bug
			eSelect.value = eSelect.options[newselectedindex].value;
			eSelect.options[newselectedindex].selected = true;
		} catch (e) {}
	}
	if (eSelect.carfunc) {eSelect.carfunc();}
	if (eSelect.popfunc) {eSelect.popfunc();}
}
/***************************************************************************
populating preview 
***************************************************************************/
function setFormToPopulate() 
{	//->void
	var el_f = document.forms[main_form];
	for (var i=0,l=el_f.elements.length;i<l;i++) 
	{ 	if (el_f.elements[i].type != 'hidden')
		{	el_f.elements[i].onchange = function() 
			{	if (this.val) {	this.val(); }
				if (this.carfunc) {this.carfunc();} 
				if (this.popfunc) {this.popfunc();} 
			}
			if (el_f.elements[i].popfunc && (el_f.elements[i].type == 'text' || el_f.elements[i].type == 'textarea'))
			{	//also update onkeyup
				el_f.elements[i].onkeyup = el_f.elements[i].popfunc;
			}
		}
	}
}
function populateCopy() 
{	//->void
	var el_f = document.forms[main_form];
	for (var i=0,l=el_f.elements.length;i<l;i++) 
	{	if (el_f.elements[i].popfunc) {el_f.elements[i].popfunc();} 
	}
}
function populateCopyElt(sEltId,index,sText) 
{	//->void
	// populate the HTML ad preview
	// and update wordcount
	//adbLog('populateCopyElt: sEltId='+sEltId+' index='+index+' sText='+sText);
	
	// Trim text and replace pound signs with escaped charcode
	// (space added to ensure separation from following content (moz))
	sText = sanitizeInput(trim(sText)).replace(new RegExp(String.fromCharCode(163),'g'),'\xa3') + ' '; 
	var s_el_span_id = sEltId + '_' + index;
	var el_span_to = document.getElementById(s_el_span_id);
	if (el_span_to) {
		try 
		{ 	el_span_to.innerHTML = sText.replace(/[\r\n]+/g,' <br class="filtered" />'); //insert breaks;
			// (a space is inserted before the break, to aid situations in CSS like ".filtered {display:none}")
			
			// To get around IE whitespace normalization
			// (http://www.quirksmode.org/bugreports/archives/2004/11/innerhtml_and_t.html)
			// we're going to horribly hack the parent element adding spaces after every span.
			if (document.all)
			{	var newhtml = el_span_to.parentNode.innerHTML.replace(/<\/span/ig,' </span');
				el_span_to.parentNode.innerHTML = newhtml;
				// adbLog('populateCopyElt: elt \''+sEltId+'\' gets='+newhtml.replace(/</g,'&lt;'));
			}
			//adbLog('populateCopyElt: replaced innerHTML');
		} catch (e) 
		{	//delete the span if innerHTML doesn't work
			el_span_to.parentNode.removeChild(el_span_to);
			adbLog('populateCopyElt: span \''+s_el_span_id+'\' deleted');
		}
	} else
	{	var el = document.getElementById(sEltId);
		if (el) 
		{ 	var el_spans = el.getElementsByTagName('span');
			var el_span_new = document.createElement('span');
			appendTextWithBreaks(el_span_new,sText);
			if (el_spans.length==0) 
			{ 	el.innerHTML=''; //zap any non-<span> content
				el.appendChild(el_span_new);
				adbLog('populateCopyElt: nochildnodes');
			}
			else 
			{ 	for (var i=0,l=el_spans.length;i<l;i++)
				{	if (el_spans[i].id.split('_')[1] > index) 
					{ 	el.insertBefore(el_span_new,el_spans[i]); 
						adbLog('populateCopyElt: insertbefore'); 
					} else if (i==l-1) 
					{ 	el.appendChild(el_span_new); 
						adbLog('populateCopyElt: final append');
					}
				}
			}
			el_span_new.id = s_el_span_id;
		}
	}
	// get wordcount + price
	popPrice(popWordCount());
	// lay out preview div (height may have changed)
	if (adbCPL) {adbCPL('force');}
}
function popWordCount() 
{	//->int
	// get some kind of wordcount for the ad
	// based on innerHTML of el_preview (global) and uses maxWords global
	var plaintext = el_preview.innerHTML.replace(/<[^>]*>/g,' ');
	//adbLog('popWordCount: plaintext='+plaintext);
	var count = count_words(plaintext);
	if (el_wordcount) 
	{	el_wordcount.innerHTML = (count>0?('for '+(count)+ simplePluralize(' word',count)):'');
	}
	//nonzero wordlimit means validate the wordcount
	if (wordlimit)
	{ 	// HACK - attach the error to the buttonnext
		if (count > wordlimit) 
		{	new validationError(document.getElementById('buttonnext'),'You have used too many words ('+count+').  The maximum is '+wordlimit+'.'+request_additional_wordc_err_msg,true);
		} else 
		{	clearError(document.getElementById('buttonnext'));
		}
		//NB. because the validation function is not registered, this error
		// will not actually prevent form submission.  This might be a good
		// thing, because it means we can get server-side wordcount.
	}
	return count;
}
function popPrice(count) 
{	//->void
	// Inform user of price
	// Uses globals el_pricequote, st_pricedetails, b_use_img
	// Base/extra prices are rounded to nearest penny *after* calculating the quote
	
	var my_count = count;
	if (b_use_img) {my_count += st_pricedetails.picturewords;}
	my_count -= st_pricedetails.basewords; //'free' words
	if (my_count < 0) {my_count = 0;}
	var pence = st_pricedetails.baseprice*100;
	//add extraprice for every additional chunk, where a chunk is *up to* extraWords words
	if (my_count>0 && st_pricedetails.extrawords>0 && st_pricedetails.extraprice>0)
	{	//adbLog('popPrice: chunks added='+Math.ceil(my_count /st_pricedetails.extrawords));
		pence += (st_pricedetails.extraprice * Math.ceil(my_count/st_pricedetails.extrawords) * 100);
	}
	pence = Math.round(pence);
	var pricestr = (st_pricedetails.baseprice==0 && pence==0)?'FREE':moneyFormat(pence/100);
	if (el_pricequote) {el_pricequote.innerHTML = pricestr;}
	pricestr = (st_pricedetails.baseprice==0 && pence==0)?' FREE':pricestr + ' inc VAT';
	//status bar action!
	window.status = 'Price: '+String.fromCharCode(163)+pricestr+' for ' + count + simplePluralize(' word',count);
} 
function appendTextWithBreaks(node,str)
{	//->node
	//appends multiple text nodes to an element turning CR|LF into ' <br />' ;
	//multiple linebreaks are merged.
	var a_texts = str.split(/[\r\n]+/);
	var e_br = document.createElement('br');
	e_br.className='filtered';
	for (var i=0,l=a_texts.length;i<l;i++)
	{	node.appendChild(document.createTextNode(a_texts[i] + ' '));
		if (i<l-1)
		{	node.appendChild(e_br.cloneNode(true));
		}
	}
	return node;
}
/***************************************************************************
GET to an iframe
***************************************************************************/
function adbIframe(url)
{	//->div elt
	adbLog('adbIframe('+url+')');
	var el_ifr_div = document.createElement('div');
	if (!b_safari) 
	{	el_ifr_div.className='adbNotShown';
		/*
		can't do this on safari cos of iframe bug:
		http://www.quirksmode.org/js/tests/safari_iframes.html
		*/
	} 
	//add iframe --IE compatible way
	el_ifr_div.innerHTML='<iframe height="0" width="0" frameborder="0" name="adbIfr" id="adbIfr" src="'+url+'"><\/iframe>';
	document.body.appendChild(el_ifr_div);
	adbLog('adbIframe:el_ifr_div.innerHTML='+el_ifr_div.innerHTML.replace(/</g,'['));
	/*
	adbLog('adbIframe: document.getElementById(\'adbIfr\')='+document.getElementById('adbIfr'));
	adbLog('adbIframe: frames[\'adbIfr\'].location.href='+frames['adbIfr'].location.href);
	adbLog('adbIframe: frames[\'adbIfr\']='+frames['adbIfr']);
	adbLog('adbIframe: frames[\'adbIfr\'].window='+frames['adbIfr'].window);
	adbLog('adbIframe: returning='+el_ifr_div);
	*/
	return el_ifr_div;
}
/***************************************************************************
style
***************************************************************************/
function selectStyle(style_id)
{	//->void
	adbLog('selectStyle: style_id='+style_id);
	var el_surrounding_div = document.getElementById('adbStyleOrderFeedback');
	if (!el_surrounding_div) {return;}
	var a_divs = el_surrounding_div.getElementsByTagName('div');
	adbLog('selectStyle: a_divs.length='+a_divs.length);
	for (var i=0,l=a_divs.length;i<l;i++)
	{ 	var func = (a_divs[i].id == 'adbStyleOrderFeedback_' + style_id)?delClassName:addClassName;
		func(a_divs[i],'adbNotShown');
	}
}
/***************************************************************************
schedule
***************************************************************************/
var elSelectedTD; //the day picked
var a_HilitedTDs = []; //the days of publication
var title_sel; //string containing comma-delimited list of title ids
function nextMonth()
{	// -> void
	if (monthIdx < a_table_ids.length-1) {setMonth(monthIdx + 1);}
	setBefUnload();
}

function prevMonth()
{	// -> void
	if (monthIdx > 0) {setMonth(monthIdx - 1);}
	setBefUnload();
}

function setMonth(idx)
{	// -> void
	for (var i=0,l=a_table_ids.length;i<l;i++)
	{	var elt = document.getElementById(a_table_ids[i]);
		if (elt) 
		{	if (idx==i) {delClassName(elt,'adbNotShown');}
			else {addClassName(elt,'adbNotShown');}
		}
	}
	monthIdx = idx;
	document.getElementById('adbPrevMoLink').style.visibility = (monthIdx == 0)?'hidden':'visible';
	document.getElementById('adbNextMoLink').style.visibility = (monthIdx+1 >= a_table_ids.length)?'hidden':'visible';
}
function clearSchedTable()
{ 	//->void
	var elSched = document.getElementById('adbSchedElt');
	if (elSched) {elSched.parentNode.removeChild(elSched);}
	addClassName(document.getElementById('adbSchedFeedback'),'adbNotShown');
	clearHilites();
}
function clearHilites()
{	//->void
	for (var i=0,l=a_HilitedTDs.length;i<l;i++)
	{	delClassName(a_HilitedTDs[i],'adbDateInRange');
	}
	a_HilitedTDs = [];
}
function hiliteTD(elTD)
{	// -> void
	if (!elTD) return;
	addClassName(elTD,'adbDateInRange');
	a_HilitedTDs[a_HilitedTDs.length] = elTD;
}
function clearSelectDateTD()
{	// -> void
	if (elSelectedTD) {delClassName(elSelectedTD,'adbDateSelected');}
	elSelectedTD=null;
	var elDate = document.getElementById('sched_select_date');
	if (elDate) {	elDate.value = ''; }
}
/*
THIS BIT NEEDS HTTPget.js
*/
function selectDateTD(elTD)
{	// -> void
	if (!elTD || elSelectedTD == elTD) return;
	var dt = elTD.id.split('_');
	var year = parseInt(dt[2],10);
	var month = parseInt(dt[3],10);
	var day = parseInt(dt[4],10);
	var elDate = document.getElementById('sched_select_date');
	if (elTD && elDate
	&& typeof year == 'number' && typeof month == 'number' && typeof day == 'number')
	{	//populate form elements
		elDate.value = year  + '-' +month + '-'+ day;
		if (elSelectedTD) delClassName(elSelectedTD,'adbDateSelected');
		addClassName(elTD,'adbDateSelected');
		elSelectedTD = elTD;
		//busy cursor
		markBusy(document.getElementById('adbScrCalendar'));
		//stop error msg
		clearError(elDate);
		//get schedule!
		//method 1 = XMLHTTPRequest (safari needs this!)
		var url = application_adb_root + 'sched_table.cfm?package_id='+package_id+'&style_id='+style_id+'&ad_id='+ad_id+'&title_sel='+title_sel+'&category_id='+category_id+'&dt='+year+'-'+month+'-'+day;
		try 
		{	var h = new HTTPget();
			h.setLogFunc(adbLog);
			// define error callback which will happen later, asynchronously
			function err(aRequest)
			{ 	adbLog("[error] bad request.  status:" + aRequest.status +" statusText:"+aRequest.statusText +"\nfalling back to iframe");
				adbIframe(url);
			}
			// the text callback is just eval()
			h.readGetRequest(url + '&format=jsexec',function(x){return eval(x);},err);
		} catch(e)
		{ 	adbLog('HTTPget failed: '+e);
			// method 2=iframe
			adbIframe(url);
		}
		if (elDate.val) elDate.val();
		//feedback
		var elFeedback = document.getElementById('adbStartDateSpan');
		if (elFeedback) {elFeedback.innerHTML = myDateString(new Date(year, month-1, day));}
	} else 
	{	clearSelectDateTD();
	}
}
function markBusy(el,isBusy) 
{	//->void
	//mark an element busy in the UI
	if ('undefined'==typeof isBusy) {isBusy=true;}
	if (isBusy) 
	{	addClassName(el,'adbWaitBusy');
	} else 
	{	delClassName(el,'adbWaitBusy');
	}
}
function acceptDomEl(el)
{	//->void
	adbLog('acceptDomEl('+el+')');
	adbLog('acceptDomEl:el.innerHTML='+el.innerHTML.replace(/\</g,'&lt;')+')');
	clearSchedTable();
	var elFeedback = document.getElementById('adbSchedFeedback');
	delClassName(elFeedback,'adbNotShown');
	// remove busy cursor
	markBusy(document.getElementById('adbScrCalendar'),false);
	// append new element to doc tree
	if (b_iewin||b_safari) // For some reason IE breaks on appendChild() if we try to import the new element directly
	{	var new_el = document.createElement(el.nodeName.toLowerCase());
		var s_ie_outerhtml = el.outerHTML;
		elFeedback.appendChild(new_el);
		new_el.outerHTML = s_ie_outerhtml;
	} else 
	{	elFeedback.appendChild(el);
	}
}
function acceptDateRange(sList) 
{	//->void
	var a_dates = sList.replace(/[-]+/g,'_').split(',');
	adbLog('acceptDateRange: a_dates='+a_dates.toString());
	for (var i=0,l=a_dates.length;i<l;i++) 
	{	// add the date to hilited
		hiliteTD(document.getElementById('adb_day_'+a_dates[i]));
	}
}
function notifyTableErr (sErr)
{	//->void
	if (sErr) 
	{ 	clearSchedTable();
		new validationError(document.getElementById('sched_select_date_day'),sErr);
	}
}
/***************************************************************************
logos
***************************************************************************/
var a_logotable_ids; //array of strings
var logo_table_idx;
var el_logo=null; //the selected logo elt
function showLogoTable(idx)
{	//->void
	for (var i=0,l=a_logotable_ids.length;i<l;i++)
	{	var elt = document.getElementById(a_logotable_ids[i]);
		if (elt) 
		{	if (i==idx) {delClassName(elt,'adbNotShown');}
			else {addClassName(elt,'adbNotShown');}
		}
	}
	logo_table_idx = idx;
	document.getElementById('adbPrevLogoLink').style.visibility = (logo_table_idx == 0)?'hidden':'visible';
	document.getElementById('adbNextLogoLink').style.visibility = (logo_table_idx+1 >= a_logotable_ids.length)?'hidden':'visible';
}
function nextLogoTable()
{	// -> void
	if (logo_table_idx < a_logotable_ids.length-1) {showLogoTable(logo_table_idx + 1);}
	setBefUnload();
}
function prevLogoTable()
{	// -> void
	if (logo_table_idx >0) {showLogoTable(logo_table_idx - 1);}
	setBefUnload();
}
function selectLogo(logo_id)
{	//->void
	adbLog('selectLogo: logo_id='+logo_id);
	var val;
	var elt = document.getElementById('adbLogoTD_'+logo_id);
	if (el_logo) {delClassName(el_logo,'adbLogoSelected');};
	if (elt) 
	{	val = logo_id;
		el_logo = elt;
		var imgsrc = o_mapLogIdSrc[logo_id];
		addClassName(elt,'adbLogoSelected');
		delClassName(document.getElementById('adbLogoFeedback'),'adbNotShown');	
		adbLog('selectLogo: getting img '+imgsrc);
		document.getElementById('adbLogoPreviewBig').src = imgsrc;
	}
	else 
	{	val='-1';
		el_logo=null;
		addClassName(document.getElementById('adbLogoFeedback'),'adbNotShown');	
	}
	document.forms[main_form].elements['logo_id'].value = val;
	setBefUnload();
}
/***************************************************************************
img upload
***************************************************************************/
function setImgUpload () 
{	addClassName(document.getElementById('adbWantToUploadImage'),'adbNotShown');
	delClassName(document.getElementById('adbUploadImageSection'),'adbNotShown');
	addClassName(document.getElementById('buttonnext'),'adbInvisible');
	document.getElementById('img_upload_yes').val = function() 
	{ 	document.getElementById('img_sub').val = document.getElementById('img_sub').onclick;
		this.val = null;
	}
}
function setImgChoice () 
{	delClassName(document.getElementById('adbWantToUploadImage'),'adbNotShown');
	addClassName(document.getElementById('adbUploadImageSection'),'adbNotShown');
	delClassName(document.getElementById('buttonnext'),'adbInvisible');
	document.getElementById('img_upload_yes').val = function () 
	{	if (this.checked) {zapErrors();setImgUpload();return false;}
		else {return true;}
	}
 	document.getElementById('img_sub').val = null;
}
var adb_upl_submit_img_default;
var adb_upl_submit_img_logo;
function adbPreUploadImgSwap (b_want_upload)
{	//->void
	// switch the src of the upload photo form initial step
	var imgsrc = (b_want_upload)?adb_upl_submit_img_logo:adb_upl_submit_img_default;
	document.getElementById('buttonnext').src = application_adb_gfx + imgsrc;
	document.getElementById('buttonnext').alt = document.getElementById('buttonnext').title = (b_want_upload)?'Submit':'Save and continue';
}
/***************************************************************************
util 
***************************************************************************/
//quirksmode.org
function findPosX(obj)
{	var curleft = 0;
	if (obj.offsetParent)
	{	while (obj.offsetParent)
		{	curleft += obj.offsetLeft;
			obj = obj.offsetParent;
		}
	}
	else if (obj.x) {curleft += obj.x;}
	return curleft;
}
function findPosY(obj)
{	var curtop = 0;
	if (obj.offsetParent)
	{	while (obj.offsetParent)
		{	curtop += obj.offsetTop;
			obj = obj.offsetParent;
		}
	}
	else if (obj.y) {curtop += obj.y;}
	return curtop;
}

