//============================================================================
//      Copyright(c) 2001-2002 by Interwoven Inc. All Rights Reserved.
//============================================================================

//****************************************************************************
//>>> JAVASCRIPT MODULE: SiteJs_IvProc
//****************************************************************************
//
// $Id$
// Synopsis: input validation processor.
// Version : 1.205
//
//
// OVERVIEW
// =====================================
// This validation module works by utilizing custom "input validation"
// attributes added to the form elements that needed checking.  So for
// example a text field that is required would use this:
//
//     <input type="text" name="Summary" value="" iv-required="1">
//
// The above says that the Summary text field is "required" (i.e. cannot
// be blank when submitted).  Note that the all of the "iv" custom
// attributes need to be in lowercase.
//
// We have three levels of checking that go on here in terms of our API:
//
// 1. Confirmation Level - validate and log error messages.
// 2. Validation Level - validate and compose a single error message.
// 3. IsA Level - validate with a simple boolean response (yay or nay).
//
// Clients of this code can rely on the funcs at any of the levels as needed.
// However, in most all cases they will just call the confirm_input() method
// which does a full confirmation and report on all form input data.
//
//
// CUSTOM ATTRIBUTES
// =====================================
// iv-status	- {on|off}
//		- the default is "on".
//		- this is good as a master switch to turn validation off,
//		  effectively "commenting out" other iv attribs.
//		- this may have other modes in the future (not just a boolean).
//
// iv-desc	- the description used during error reporting.
//		- if set to "%i" then only the iv-item will be used.
//
// iv-item	- the itemized description used during error reporting.
//		- "itemization" means "numbering the input form elements".
//		- this is typically a "question number" from a form.
//
// iv-type	- {text|email|phone|money|url|int|float|word|aword}
//		- the default is "text" (good old generic text).
//		- not all types are currently implemented, just these:
//		- email, phone, int, word, aword
//
// iv-required	- {1|0} (required value).
//		- the most common attribute set on a text field.
//
// iv-reqother	- {1|0} (required other value).
//		- this is typically used when a pulldown is set to "Other".
//		- and a text field is available to describe what "Other" means.
//              - note: using iv-reqif can get the same effect as this and is
//		- more flexible in other cases of setting constraints.
//
// iv-reqif	- "name|is|val"   (required if name's value is set to "val").
// 		- "name|like|pat" (required if name's value matches "pat").
//
// iv-reqmatch	- "name" (required to match name's value).
//
// iv-pattern	- generic regex matching.
//
// iv-min	- minimum value for a number.
//
// iv-max	- maximum value for a number.
//
// iv-minlen	- minimum length (num chars) for the input.
//
// iv-maxlen	- maximum length (num chars) for the input.
//
// iv-notrim	- turn head/tail whitespace removal off for a textfield.
//
// iv-realemail - {0|1}
//		- checks to make sure an email address is NOT anonymous.
//		- anonymous addy's are things like yahoo.com, hotmail.com, etc.
//		- the list of anon addys is okay but definitely not complete.
//
// iv-evilemail - {0|1}
//		- checks to make sure an email address is NOT from a competitor.
//		- thus addresses like: documentum, stellent, vignette, etc.
//
//
// OTHER DESIRED FEATURES
// =====================================
// - need a method to turn all validation off across all elements.
//
//
// NOTES
// =====================================
// There are some issues with the way CGI.pm generates custom attributes.
// The older version generated UPPERCASE.  The newer version does lowercase.
// For now we force lowercase output in perl.
//
// For the time being this module is coded as stand-alone as possible.
// Currently it can function without the need to include any other javascript
// library functionality.  There are things we could factor out in the
// future (email syntax checking, polling radio group's, etc).
//
//
// HISTORY
// =====================================
// 2001.12.20
//	-- Created by Akshat Pramod (asharma@interwoven.com).
//
//****************************************************************************

//-- globals -----------------------------------------------------------------
//----------------------------------------------------------------------------
//
// Symbollic names for attributes to avoid single quoting them hither & yon.
// We prefix them with underscore to avoid potential function name conflicts.
//
var _iv_status	  = 'iv-status';
var _iv_desc	  = 'iv-desc';
var _iv_item	  = 'iv-item';
var _iv_type	  = 'iv-type';
var _iv_required  = 'iv-required';
var _iv_reqother  = 'iv-reqother';
var _iv_reqif	  = 'iv-reqif';
var _iv_reqmatch  = 'iv-reqmatch';
var _iv_min	  = 'iv-min';
var _iv_max	  = 'iv-max';
var _iv_minlen	  = 'iv-minlen';
var _iv_maxlen	  = 'iv-maxlen';
var _iv_notrim	  = 'iv-notrim';
var _iv_pattern	  = 'iv-pattern';
var _iv_realemail = 'iv-realemail';
var _iv_evilemail = 'iv-evilemail';


//-- instancing --------------------------------------------------------------
//----------------------------------------------------------------------------
//
// IvProc - constructor.
//
function SiteJs_IvProc(form)
{
    //--------------------------------------
    // Initialize class methods.
    //

    // command ops
    this.cmd_validate = SiteJs_IvProc_cmd_validate;
    this.cmd_submit = SiteJs_IvProc_cmd_submit;
    this.cmd_reset = SiteJs_IvProc_cmd_reset;
    this.cmd_process = SiteJs_IvProc_cmd_process;

    // attribute ops 
    this.attrib_init = SiteJs_IvProc_attrib_init;
    this.attrib_init_needed = SiteJs_IvProc_attrib_init_needed;
    this.attrib_init_data = SiteJs_IvProc_attrib_init_data; // caller override
    this.attrib_set = SiteJs_IvProc_attrib_set;
    this.attrib_config = SiteJs_IvProc_attrib_config;

    // confirmation ops
    this.confirm_input = SiteJs_IvProc_confirm_input;
    this.confirm_required = SiteJs_IvProc_confirm_required;
    this.confirm_reqother = SiteJs_IvProc_confirm_reqother;
    this.confirm_reqif = SiteJs_IvProc_confirm_reqif;
    this.confirm_reqmatch = SiteJs_IvProc_confirm_reqmatch;
    this.confirm_email = SiteJs_IvProc_confirm_email;
    this.confirm_phone = SiteJs_IvProc_confirm_phone;
    this.confirm_word = SiteJs_IvProc_confirm_word;
    this.confirm_aword = SiteJs_IvProc_confirm_aword;
    this.confirm_pattern = SiteJs_IvProc_confirm_pattern;
    this.confirm_length = SiteJs_IvProc_confirm_length;
    this.confirm_int = SiteJs_IvProc_confirm_int;

    // validation ops
    this.validate_required = SiteJs_IvProc_validate_required;
    this.validate_email = SiteJs_IvProc_validate_email;
    this.validate_phone = SiteJs_IvProc_validate_phone;
    this.validate_word = SiteJs_IvProc_validate_word;
    this.validate_aword = SiteJs_IvProc_validate_aword;
    this.validate_pattern = SiteJs_IvProc_validate_pattern;
    this.validate_anon_email = SiteJs_IvProc_validate_anon_email;
    this.validate_evil_email = SiteJs_IvProc_validate_evil_email;
    this.validate_length = SiteJs_IvProc_validate_length;
    this.validate_float = SiteJs_IvProc_validate_float;
    this.validate_int = SiteJs_IvProc_validate_int;
    this.validate_csv = SiteJs_IvProc_validate_csv;

    // is-a ops
    this.is_float = SiteJs_IvProc_is_float;
    this.is_int = SiteJs_IvProc_is_int;
    this.is_blank = SiteJs_IvProc_is_blank;

    // error log ops
    this.elog_count = SiteJs_IvProc_elog_count;
    this.elog_clear = SiteJs_IvProc_elog_clear;
    this.elog_prepend = SiteJs_IvProc_elog_prepend;
    this.elog_append = SiteJs_IvProc_elog_append;
    this.elog_alert = SiteJs_IvProc_elog_alert;
    this.elog_compose = SiteJs_IvProc_elog_compose;

    // debug utils
    this.dump_props = SiteJs_IvProc_dump_props;

    // misc utils
    this.get_value = SiteJs_IvProc_get_value;
    this.get_desc = SiteJs_IvProc_get_desc;
    this.poll_btn_group = SiteJs_IvProc_poll_btn_group;
    //this.poll_radio_group = SiteJs_IvProc_poll_radio_group;
    this.poll_option_menu = SiteJs_IvProc_poll_option_menu;
    this.trim  = SiteJs_IvProc_trim;
    this.ltrim = SiteJs_IvProc_ltrim;
    this.rtrim = SiteJs_IvProc_rtrim;

    //--------------------------------------
    // Initialize class member data.
    //
    this.form = form;				// the form we are validating
    this.elem_name = '';			// current element name
    this.elem = 0;				// current element object
    this.elog = new Array();			// error log
    this.attrib_initd = 0;			// attributes initd?

    return (this);
}


//-- cmd ops -----------------------------------------------------------------
//----------------------------------------------------------------------------
//
// cmd_validate - handle callbacks that validate a form.
//
// This function is designed to be called by a <form> tag's onSubmit handler.
//
// Returns: true if the form can be submitted; false otherwise.
//
function SiteJs_IvProc_cmd_validate()
{
    var form = this.form;

    // Initialize custom attributes for certain browsers.
    this.attrib_init();

    // Check their input and display a dialog of errors if needed.
    var valid = this.confirm_input('default', 'default');

    // Clear out the log of what the user just saw for error messages.
    this.elog_clear();

    // Return the answer.
    return (valid);
}


//
// cmd_submit - handle callbacks that validate a form and then submit it.
//
function SiteJs_IvProc_cmd_submit()
{
    var form = this.form;

    // Initialize custom attributes for certain browsers.
    this.attrib_init();

    // If we fail validation, an alert is shown and the submit is voided.
    if (this.confirm_input('default', 'default')) { form.submit(); }

    // Clear out the log of what the user just saw for error messages.
    this.elog_clear();
}


//
// cmd_reset - handle callbacks that reset the form (for symmetry mostly).
//
function SiteJs_IvProc_cmd_reset()
{
    this.form.reset();
}


//
// cmd_process - handle callbacks that validate a form and then submit it.
//
// This is a special version of "submit" that allows the caller to pass
// a "command symbol" back to the cgi as a hidden value in the form.
// The purpose of this is to allow multi-page forms to indicate whether
// the submission is meant to go "back", or "next", or "finish", or "reset".
//
// >> cmd_elem  - the form element that receives the cmd parameter.
// >> cmd	- the command to process.
//
function SiteJs_IvProc_cmd_process(cmd_elem, cmd)
{
    // Locate the form and assign the command.
    var form = cmd_elem.form;			// i.e. this elem's "parent"
    cmd_elem.value = cmd;			// store the cmd in the form

    // If we are resetting then it's simple.
    if (cmd == 'reset') { form.reset(); return; }

    // If we are backing up then we do not have to validate.
    if (cmd == 'back') { form.submit(); return; }

    // We now presume we are doing a 'next', or a 'finish', etc.
    // This implies we need bulk input validation.

    // Initialize custom attributes for certain browsers.
    this.attrib_init();

    // If we fail validation, an alert is shown and the submit is voided.
    if (this.confirm_input('default', 'default')) { form.submit(); }

    // Clear out the log of what the user just saw for error messages.
    this.elog_clear();
}


//-- attribute ops -----------------------------------------------------------
//----------------------------------------------------------------------------
//
// attrib_init - make sure attributes are init'd if necessary.
//
// Returns: flag stating if attrib_init_data() was called or not.
//
function SiteJs_IvProc_attrib_init()
{
    if (this.attrib_initd) {
	return (0);
    }
    else if (this.attrib_init_needed()) {
	this.attrib_init_data();
	this.attrib_initd = 1;
	return (1);
    }
    else {
	return (0);
    }
}


//
// attrib_init_needed - state whether attributes should be programatically set.
//
function SiteJs_IvProc_attrib_init_needed()
{
    var is_msie = (navigator.appName.indexOf("Microsoft") != -1);
    return (! is_msie);
}


//
// attrib_init_data - a no-op which the caller should override.
//
// This function exists because netscape navigator/communicator does not
// allow for setting custom attributes on elements directly in the html.
// In order for the attributes to show up, they must be procedurally set
// via javascript. IE can get by on just the HTML tag definitions alone.
//
// When this function is defined by the caller it should end up with code
// like this:
//
// var form = this.form;
// form.name['iv-required'] = 1;
// form.email['iv-required'] = 1;
// form.email['iv-type'] = 'email';
//
// Or like this:
//
// this.attrib_set('iv-required', '1', 'user_name', 'user_email');
// this.attrib_set('iv-type', 'email', 'user_email', 'comp_email');
// this.attrib_set('iv-type', 'int', 'num_employees', 'num_years');
//
// A combination of both methodologies is also possible.
//
// The caller should define one of two functions:
//
// 1. SiteJs_IvProc_attrib_init_data - to override this method.
// 2. iv_attrib_init_data - to provide their own function.
//
// The latter function receives "us" as it's parameter.
// Letting them define their own function is mostly a matter of notational
// convenience as "SiteJs_IvProc_" is a mouthful.
//
function SiteJs_IvProc_attrib_init_data()
{
    var rc = 0;
    if (typeof(iv_attrib_init_data) == 'function') {
	rc = iv_attrib_init_data(this);
    }
    return (rc);
}


//
// attrib_set - set an attribute value for a list of elements.
//
// This goes out of it's way to be safe in case we screw up our data.
//
function SiteJs_IvProc_attrib_set(attrib, val /* and then a list of eids */ )
{
    var e;
    for (var i=2; i<arguments.length; ++i) {
	e = this.form[arguments[i]];
	if (e) { e[attrib] = val; }
    }
}


//
// attrib_config - set a bunch of attributes on a single element.
//
// Returns: the number of name / value pairs processed.
//
function SiteJs_IvProc_attrib_config(eid /* and then pairs of names/values */ )
{
    var e = this.form[eid];
    if (! e) { return (0); }
    var limit = arguments.length - 1;
    var count = 0;
    var name, value;
    for (var i=1; i<limit; i+=2) {
	name = arguments[i+0];
	value = arguments[i+1];
	if (name) { e[name] = value; }
	++count;
    }
    return (count);
}


//-- confimation ops ---------------------------------------------------------
//----------------------------------------------------------------------------
//
// confirm_input - check all values in the form using attributes for hints.
//
// There are two phases to checking a given element.
//
// 1. An existance check for a non-blank value if it is required.
// 2. A syntax check.
//
function SiteJs_IvProc_confirm_input(prefix, suffix)
{
    var e;					// form element object
    var i;					// loop index
    var type;					// the type of the value
    var val;					// generic value
    var gtable = new Object();			// group hit table
    var group;					// group element
    var is_group;				// is it a group?

    for (i=0; i<this.form.length; ++i) {
	//--------------------------------------
	// Get a handle to the element and make sure it wants validation.
	//
	e = this.form.elements[i];
	//if (e.name == 'Name') { this.dump_props(e); dumped = 1; }
	val = e[_iv_status];
	if (val == 'off') { continue; }

	//--------------------------------------
	// Skip this if the element has already been checked via a group.
	//
	if (gtable[e.name]) { continue; }

	//--------------------------------------
	// Fold group elements to a single element.
	// This is done by monitoring which groups we've done in the gtable.
	// And we only allow the first element of a group to "speak for it".
	// Both checkboxes and radio buttons can be grouped.
	//
	type = e.type;
	if (type == 'checkbox' || type == 'radio') {
	    group = this.form[e.name];
	    if (group.length > 1) {
		gtable[e.name] = 1;	// this group has been seen
		is_group = true;	// we are in "group mode" now

		// Transfer the first group elem's props to the group itself.
		// xxx: it would be better to have a func to loop on props.
		//
		// We only do the transfer if the group "seems" to have not
		// already been given "iv" attributes.  It *will* already
		// have them when running under netscape due to the way we
		// assign attributes procedurally.  This is because we assign
		// the attributes to the group itself, not the individual elems.
		//
		if (! group[_iv_item]) {
		    group[_iv_required] = e[_iv_required];
		    group[_iv_reqother] = e[_iv_reqother];
		    group[_iv_reqif] = e[_iv_reqif];
		    group[_iv_item] = e[_iv_item];
		    group[_iv_desc] = e[_iv_desc];
		    group[_iv_type] = e[_iv_type];
		}

		// Switch over to using the group as the element.
		e = group;
	    }
	}

	//--------------------------------------
	// Trim textfields unless the iv-notrim attribute is there.
	//
	if (type == 'text') {
	    var notrim = e[_iv_notrim] == '1';
	    if (! notrim) {
		var curr_value = e.value;
		var trim_value = this.trim(curr_value);
		if (trim_value != curr_value) {
		    e.value = trim_value;
		}
	    }
	}

	//--------------------------------------
	// Confirm existance.
	//
	if (e[_iv_required] == '1') {
	    if (! this.confirm_required(e)) { continue; }
	}
	else if (e[_iv_reqother] == '1') {
	    if (! this.confirm_reqother(e)) { continue; }
	}
	else if (e[_iv_reqif]) {
	    if (! this.confirm_reqif(e)) { continue; }
	}
	else if (e[_iv_reqmatch]) {
	    if (! this.confirm_reqmatch(e)) { continue; }
	}

	//--------------------------------------
	// Confirm size limits.
	//
	if (! this.confirm_length(e)) { continue; }

	//--------------------------------------
	// Confirm syntax based on type.
	//
	type = e[_iv_type];
	if (! type) { type = 'text'; }
	if (type == "email") {
	    if (! this.confirm_email(e)) { continue; }
	}
	else if (type == 'phone') {
	    if (! this.confirm_phone(e)) { continue; }
	}
	else if (type == 'word') {
	    if (! this.confirm_word(e)) { continue; }
	}
	else if (type == 'aword') {
	    if (! this.confirm_aword(e)) { continue; }
	}
	else if (type == 'int') {
	    if (! this.confirm_int(e)) { continue; }
	}

	//--------------------------------------
	// Confirm syntax based on generic pattern.
	//
	var pat_str = e[_iv_pattern];
	if (pat_str) {
	    if (! this.confirm_pattern(e)) { continue; }
	}
    }

    // If there were any errors, display them via an alert and return false.
    // XXX: it would be wise to limit the number of errors displayed.
    if (this.elog_count()) { this.elog_alert(prefix, suffix); return (false); }

    // The input was good.
    return (true);
}


//
// confirm_required - process a required element.
//
function SiteJs_IvProc_confirm_required(elem)
{
    var val = this.get_value(elem);
    var blank = val == null || this.is_blank(val);
    if (blank) {
	this.elog_append(this.get_desc(elem) + ' is blank.');
	return (false);
    }
    return (true);
}


//
// confirm_reqother - process a required-other element.
//
// This type of required value is a bit different.  A "required other"
// value occurs when a select menu has been set to "other", and the
// form designer would like to require that the user specify in a text
// field/area what they mean by "other".
//
function SiteJs_IvProc_confirm_reqother(elem)
{
    var val = this.get_value(elem);
    if (val != "Other") { return (true); }

    //
    // The select menu is "Other", so check the text field.
    // We enforce a specific naming convention - the text field must have
    // the same name as the select menu but with "_other" added to it.
    // We could extend this later such that the -iv-other resource itself
    // could name the other text field.
    //
    var other_name = elem.name + '_other';
    var other_elem = this.form[other_name];
    if (! other_elem) { return (true); }		// sanity check
    val = this.get_value(other_elem);
    var blank = val == null || this.is_blank(val);
    if (blank) {
	this.elog_append(this.get_desc(elem)
	    + ' is set to Other so please specify it further.'
	);
	return (false);
    }
    return (true);
}


//
// confirm_reqif - process a required-if element.
//
// A "required if" value occurs when one form element is required based
// on another form element being "set" to some value.
//
// Syntax:  -iv-reqif => '<elem_name>|<mode>|<value>'
//
// Mode: "is"
// Example: 'user_location|is|custom'
// Means  : this element is required IF the user_location is set to 'custom'.
//
// Mode: "like"
// Example: 'comp_name|like|^IBM.*'
// Means  : this element is required IF the comp_name matches the pat IBM.*.
//
// Obviously the logic to "required if" could be infinitely complex.
// The present syntax atleast allows us to have a few modes of checking.
// It's mainly useful when a set of fields become required because a
// checkbox is set or a radio group has a certain value.
//
// In hindsight, using '|' as the separator char was not the best choice
// due to the collision with it's value in RegExp.
//
// XXXBUG: The John Bug
// XXXBUG: this was hacked to get around the problem jdoumani's browser had.
// XXXBUG: instead of using v.shift()'s it does a slice().
//
function SiteJs_IvProc_confirm_reqif(elem)
{
    var cond = elem[_iv_reqif];
    var v = cond.split('|');
    var src_name = v[0];
    var mode = v[1];
    var tmp = v.slice(2);
    var thing = tmp.join('|');

    // Lookup the source element and get it's value.
    var src_elem = this.form[src_name];
    var src_val = this.get_value(src_elem);

    if (mode == 'is') {
	if (src_val == thing) { return (this.confirm_required(elem)); }
    }
    else if (mode == 'like') {
	var pat = new RegExp(thing);
	if (pat.test(src_val)) { return (this.confirm_required(elem)); }
    }
    return (true);
}


//
// confirm_reqmatch - process a required-match element.
//
// This is useful when a confirmation password must be entered and
// must match the originally typed password.
//
// Syntax:  -iv-reqmatch => '<elem_name>'
//
// XXX: javascript will not allow looking at password vals so this is a no go.
//
function SiteJs_IvProc_confirm_reqmatch(elem)
{
    var src_name = elem[_iv_reqmatch];
    var src_elem = this.form[src_name];
    var src_val = this.get_value(src_elem);
    var our_val = this.get_value(elem);

    if (our_val != src_val) {
	this.elog_append(''
	    + this.get_desc(elem) + ' does not match '
	    + this.get_desc(src_elem)
	);
	return (false);
    }

    return (true);
}


//
// confirm_email - process an email element.
//
function SiteJs_IvProc_confirm_email(elem)
{
    var val = this.get_value(elem);
    var blank = val == null || this.is_blank(val);
    if (!blank || (blank && elem[_iv_required])) {
	var result = this.validate_email(val);
	if (result != 'valid') {
	    this.elog_append(this.get_desc(elem) + ' ' + result);
	    return (false);
	}

	if (elem[_iv_realemail] == "1") {
	    result = this.validate_anon_email(val);
	    if (result == 'valid') {	// Here 'valid' is "not good" :-)
		// This needs to be a real email but is in fact anonymous.
		this.elog_append(this.get_desc(elem) + ' '
		    + "is restricted.\n\n"
		    + "The email address you have entered matches a list of\n"
		    + "restricted generic addresses (like yahoo.com,\n"
		    + "hotmail.com, etc). You should enter your employee\n"
		    + "email address for the company where you work.\n"
		);
		return (false);
	    }
	}

	if (elem[_iv_evilemail] == "1") {
	    result = this.validate_evil_email(val);
	    if (result == 'valid') {	// Here 'valid' is "not good" :-)
		// An evil competitor is trying to use our products & services!
		// EEEK!  Raise the draw-bridge!  Circle the wagons!
		this.elog_append(this.get_desc(elem) + ' '
		    + "is restricted.\n\n"
		    + "The email address you have entered matches a list of\n"
		    + "restricted addresses.\n"
		);
		return (false);
	    }
	}
    }
    return (true);
}


//
// confirm_phone - process a phone number element.
//
function SiteJs_IvProc_confirm_phone(elem)
{
    var val = this.get_value(elem);
    var blank = val == null || this.is_blank(val);
    if (!blank || (blank && elem[_iv_required])) {
	var result = this.validate_phone(val);
	if (result != 'valid') {
	    var str = this.get_desc(elem) + ' ' + result + '.';
	    var pat = /invalid char/;
	    if (pat.test(result)) {
		str += "\n";
		str += "    - Valid characters are:\n";
		str += "    - digits, space, dash, plus, period, and parens\n";
		str += "    - and the letter 'x' for extensions.";
	    }
	    this.elog_append(str);
	    return (false);
	}
    }
    return (true);
}


//
// confirm_word - process a word element.
//
function SiteJs_IvProc_confirm_word(elem)
{
    var val = this.get_value(elem);
    var blank = val == null || this.is_blank(val);
    var missing = blank && elem[_iv_required];
    if (! blank) {
	var result = this.validate_word(val);
	if (result != 'valid') {
	    this.elog_append(this.get_desc(elem) + ' - ' + result + '.');
	    return (false);
	}
    }
    return (true);
}


//
// confirm_aword - process an alpha-word element.
//
function SiteJs_IvProc_confirm_aword(elem)
{
    var val = this.get_value(elem);
    var blank = val == null || this.is_blank(val);
    var missing = blank && elem[_iv_required];
    if (! blank) {
	var result = this.validate_aword(val);
	if (result != 'valid') {
	    this.elog_append(this.get_desc(elem) + ' - ' + result + '.');
	    return (false);
	}
    }
    return (true);
}


//
// confirm_pattern - process an element against a generic pattern.
//
function SiteJs_IvProc_confirm_pattern(elem)
{
    var pat_str = elem[_iv_pattern];
    if (! pat_str) { return (true); }

    var val = this.get_value(elem);
    var blank = val == null || this.is_blank(val);
    var missing = blank && elem[_iv_required];
    if (! blank) {
	var result = this.validate_pattern(val, pat_str);
	if (result != 'valid') {
	    this.elog_append(this.get_desc(elem) + ' - ' + result + '.');
	    return (false);
	}
    }
    return (true);
}


//
// confirm_length - check the string length of the input.
//
function SiteJs_IvProc_confirm_length(elem)
{
    // First make sure we have some size limits.
    var minlen = elem[_iv_minlen];
    var maxlen = elem[_iv_maxlen];
    var have_min = minlen != null && minlen > 0;
    var have_max = maxlen != null && maxlen > 0;
    if (!have_min && !have_max) { return (true); }

    // We need to provide some constraints so lookup the value of the elem.
    var val = this.get_value(elem);

    // Check the constraints.
    var blank = val == null || this.is_blank(val);
    var missing = blank && elem[_iv_required];
    if (! blank) {
	var result = this.validate_length(val, minlen, maxlen);
	if (result != 'valid') {
	    var name = this.get_desc(elem);
	    this.elog_append(name + ' - ' + result);
	    return (false);
	}
    }

    return (true);
}


//
// confirm_int - process an integer element.
//
function SiteJs_IvProc_confirm_int(elem)
{
    var val = this.get_value(elem);
    var blank = val == null || this.is_blank(val);
    if (!blank || (blank && elem[_iv_required])) {
	var result = this.validate_int(val);
	var name = this.get_desc(elem);
	if (result != 'valid') {
	    this.elog_append(name + ' ' + result);
	    return (false);
	}

	// Check the minimum limit for this integer.
	var min = elem[_iv_min];
	if (min != null && val < parseInt(min)) {
	    this.elog_append(name + ' must not be less than ' + min + '.');
	    return (false);
	}

	// Check the maximum limit for this integer.
	var max = elem[_iv_max];
	if (max != null && val > parseInt(max)) {
	    this.elog_append(name + ' must not be greater than ' + max + '.');
	    return (false);
	}
    }
    return (true);
}


//-- validation ops ----------------------------------------------------------
//----------------------------------------------------------------------------
//
// validate_required - verify that a required value is "there".
//
// Returns: a string; "valid" means a good address; otherwise it's an error msg.
//
function SiteJs_IvProc_validate_required(val)
{
    if (val == null || val == '' || this.is_blank(val)) {
	return ('value is blank.');
    }
    return ('valid');
}


//
// validate_email - verify that an email address is valid.
//
// Returns: a string; "valid" means a good address; otherwise it's an error msg.
//
function SiteJs_IvProc_validate_email(email)
{
    // Perform some simple sanity checks with clear error messages.
    if (email == "") {
	return ('address is blank.');
    }
    else if (email.indexOf(' ') >= 0) {
	return ('address has a space in it.');
    }
    else if (email.indexOf("\t") >= 0) {
	return ('address has a tab in it.');
    }
    else if (email.indexOf(',') >= 0) {
	return ('address has a comma "," in it.');
    }
    else if (email.indexOf('#') >= 0) {
	return ('address has a pound "#" in it.');
    }
    else if (email.indexOf('!') >= 0) {
	return ('address has an exclamation "!" in it.');
    }
    else if (email.indexOf('@') < 0) {
	return ('address has no "@" symbol.');
    }

    // Perform a more thorough pattern-match but a generic error message.
    //var pat = /^[a-z][a-z_0-9\.]*@[a-z_0-9\.]+\.[a-z]{2,4}$/i;
    //var pat = /^[a-z][a-z_0-9\.\-]*@[a-z_0-9\.\-]+\.[a-z]{2,4}$/i; orig
    //var pat = /^[a-z][a-z_0-9\'\.\-]*@[a-z_0-9\.\-]+\.[a-z]{2,4}$/i; latest
    var pat = /^[a-z_0-9\'\.\-]*@[a-z_0-9\.\-]+\.[a-z]{2,4}$/i;
    var valid = pat.test(email);
    if (! valid) { return ('address format is bad.'); }

    // Indicate a positive result.
    return ('valid');
}


//
// validate_phone - verify that a phone number is valid.
//
// A phone number must only consist of:
// - digits, spaces, dashes, plus-signs, dots, and parentheses.
// - the letter 'x' in case they mention an extension as in x1234.
// - and it must contain atleast 1 digit.
//
// Returns: a string; "valid" means a good address; otherwise it's an error msg.
//
function SiteJs_IvProc_validate_phone(phone)
{
    // Perform some simple sanity checks with clear error messages.
    if (phone == "") { return ('is blank'); }

    // Check for invalid characters.
    var pat = /[^0-9 \-+\.()xX]/;	// everything-but (^) pattern
    var hit = pat.test(phone);
    if (hit) { return ('has invalid character(s)'); }

    // Make sure there is atleast one digit.
    pat = /[0-9]/;
    var hit = pat.test(phone);
    if (! hit) { return ('has no digits'); }

    // Indicate a positive result.
    return ('valid');
}


//
// validate_word - verify a string has basic alphanumeric chars.
//
// A word is composed of just letters, numbers, and underscores.
// It may start with any of those characters.
// This is good for things like usernames which should not have punctuation
// characters in them.
//
// Returns: a string; "valid" means a good word; otherwise it's an error msg.
//
function SiteJs_IvProc_validate_word(word)
{
    if (word != '') {
	var pat = /^\w+$/;
	if (! pat.test(word)) {
	    return('invalid input (use only letters, digits, and underscores)');
	}
    }

    return ('valid');
}


//
// validate_aword - verify a string has basic alphanumeric chars.
//
// An 'a' word ("alpha word") is composed of letters, numbers, and underscores;
// And it must start with an alphabetic letter or an underscore.
// This is good for things like usernames which should not have punctuation
// characters in them.
//
// Returns: a string; "valid" means a good word; otherwise it's an error msg.
//
function SiteJs_IvProc_validate_aword(aword)
{
    if (aword != '') {
	var pat1 = /^[A-Za-z_]/;
	if (! pat1.test(aword)) {
	    return('invalid input (must start with a letter or underscore)');
	}
	var pat2 = /^[A-Za-z_]\w*$/;
	if (! pat2.test(aword)) {
	    return('invalid input (use only letters, digits, and underscores)');
	}
    }

    return ('valid');
}


//
// validate_pattern - verify a string against a generic pattern.
//
// Returns: a string; "valid" means good syntax; otherwise it's an error msg.
//
function SiteJs_IvProc_validate_pattern(value, pat_str)
{
    var pat = new RegExp(pat_str);
    if (! pat.test(value)) {
	return('invalid input (does not have expected syntax)');
    }
    return ('valid');
}


//
// validate_anon_email - verify that an email address is "generic".
//
// Returns: a string; "valid" means a good address; otherwise it's an error msg.
//
// This is a basic set of anonymous address patterns.  It is by no
// means complete.  There is a corresponding "anon_email()" function
// in the SiteDev::CgiApp class with the same patterns.
//
function SiteJs_IvProc_validate_anon_email(email)
{
    //var host_pat = /(\@|\.)(yahoo|excite|lycos|hotmail|aol|earthlink|msn|compuserve|juno|netzero|nightmail|rocketmail|newmail|runbox|hushmail|fastmail|rediffmail|mailcity|bremail|icqmail)\./i;
    var host_pat = /(\@)(Murex|endeca|fast|Misys|Calypso|Bloomberg|TZero|Swapswire|Communicator|Thunderhead|DocumentSciences|Superderivatives|Omgeo|jpw3|sitecore|iprismglobal|dilnekaha|sivrin|syntegix|hitechinfosoft|msuyazilim|actlit|pb|aws|systemsalliance|westcomzivo|tempinbox|zdus|inputpattern|proxource|rayatworld|mailingaddress|panoptic|163|ionglobal|hotmaill|indiaties|goowy|vassallo|your2cents|pookmail|crowther|lejcreative|ftftech|carvetech|clientprofiles|certus|iitiim|126|3web|ya|ttla|netupinc|goodstarthosting|windtalkersystems|electronichelp|humanusblue|blsny|21CN|263|2organize|4dv|37|8lot|abcd|accutrac|access4less|adelphia|amplexor|angelfire|aspective|atmedia|attbi|aol|aracnet|arabia|aventuremail|baol|dhaka|btinternet|jbcasse|kondiment|ecom-consulting|bigfoot|bigpond|borland|boursama|bowkett|bremail|brmail|canada|caramail|charter|chartermi|chello|chinabyte|centerus|techie|howardforums|goodstarthosting|doramail|zwallet|mymail|chinaren|chollian|citilink|club-internet|core|comcast|company|compuserve|consultant|credera|ctouch|coolgoose|jajmaheu|isatechnologies|sunrisecom|vsnl|joimail|dodgeit|gatgar|kontentsu|thebizramp|spunge|arbogast|suscom|nac|asu|dacafe|devestup|dhapdigital|digitalsanctuary|directtvinternet|direcway|dreamwiz|earthlink|earthwire|ebtelco|email|emailaccount|emailkate|morpheus-consulting|fork|eqqu|argentina|comspec|emc|emcity|empal|engineer|entouchonline|entelchile|epam|eresmas|eroom|erntech|esmas|etang|eudoramail|e-volution|excite|eyou|ezqq|fastimap|fastmail|fathom|fea|fennagroup|fluentminds|fortaleza|freenet|freeserve|funkystage|fuzetto|gawab|gigigaga|geind|gepex|gnarff|gmail|gmx|go|google|goelz|hanmail|hcl|hinet|hongkong|hotbox|hotmail|eplus|dsl.pipex|playful|directorsdesk|slashwave|algorismtech|crmtogo|infostoria|simedia|philayton.f2s|quorumhq|slk-ites|erols|operamail|idsscan|getronics|tavant|aniruch|tamgroup|infocusindore|virtusa|virtusa|scuttlefish|itworx|humlog|mdy|giant-squid|vtr|touchtelindia|bit-slap|mankekar|mailsnare|hotpop|hushmail|hypercomm|illinios|iamwaiting|ifrance|igenious|impervious|iname|india|indiainfo|indiatimes|intizen|intelladoc|internetfiji|internetdrive|jtetechnical|j51|jas|jaybert|juno|laposte|legalkey|lib1|libero|linuxmail|lycos|lycos-europe|mac|magyck|mailasia|mailcity|mailcompany|modblog|5gbx|mailinator|masterlogin|mantramail|mediarush|mindless|mindspring|mipiq|mixmail|mn|mnsi|mrecantile|msn|mts|muchomail|myfastmail|mycompany|myrealbox|myinetmail|myrealbox|myway|nate|netcom|neteasy|netexecutive|netscape|netzero|netcourrier|netvigator|netzoola|newmail|nexgo|nexicom|nightmail|none|ntierlogic|ntlworld|nyc|oz|object1|oncecorp|onebox|openix|optonline|outwardbox|planet|poboxes|portugalmail|post|prodigy|proximus|protechsoft|provide|puebla|rediff|rediffmail|rediffmailk|sparqnet|onshoresoftware|bluebottle|spymac|transputec|asuku|starmail|rcn|rocketmail|runbox|sabbagh|sbcglobal|scoop|seanet|sfi|sify|simplicity-works|sina|sinaman|streamyx|smyr|solcara|sohu|softad|softhome|spamcop|speedpost|spies|spookmedia|stancerlee|storm|supanet|sunnyind|swbell|symbol|sympactico|symbionet|tank23|tampabayrr|tds|teluguone|technicalstudio|tellurian|terra|the-pub|timco|times|tin|t-online|topletter|tom|twanadoo|vsoint|ucs|uk2|unitel|ureach|usa|uunet|verizon|viant|vishalit|visi|vnsl|voodoo|walla|wanadoo|web|webcruiser|webmail|webmall|wellho|weppy|whale-mail|wowway|www|xerdict|xitex|xmsg|xor|xtra|yahoo|yahoo1|yahoox|yam|yeah|zettaworks|superonline|outsystems|semanticspace|zonnet)\.(com|net|org|gov|biz|edu|info|int|mil)/i;
    var host_match = host_pat.test(email);

    if (! host_match) {
	host_pat = /(\@)(mail|verio)\.com$/i;
	host_match = host_pat.test(email);
    }

    if (! host_match) {
	host_pat = /(\@)(att|covad|cox|connecti|comporium|eircom|graffiti|ecosse|ml1|netscape|palleonmail|speakeasy|softcom|ucs)\.net$/i;
	host_match = host_pat.test(email);
    }

    if (! host_match) {
	host_pat = /(\@)(evtek|parsec|dewlock|dodo|tpg|optus|phs|iprimus|fesco|lexxa|mabuhay|noggin|swiftdsl|singnet|ig|optusnet|uol|terra|ssol|url|yahoo|yp|pchome)\.com\.(br|it|au|cn|nl|mo|sg|uk|fr|it|tw|be)$/i;
	host_match = host_pat.test(email);
    }

    if (! host_match) {
	host_pat = /(\@)(auroville|ix|columbus|chn|nbnet|google|pp|gmail|consultant|cable|austin|carolina|innova|internode|houston|ibm|nyc|nycap|rochester|ms|nc|neo|vip|socal|srinivas|twmi|tampabay)\.(every1|google|gmail|nb|inet|netcom|comcast|hinet|on|sina|stph|sympatico|rr|org|aithent|vodafoneomnitel)\.(com|net|fi|in|it|ca)$/i;
	host_match = host_pat.test(email);
    }

    if (! host_match) {
	host_pat = /(\@)(acslink|bluepoint|navitas|pacific|socialchange|seed|time|dodo|island|tm)\.net\.(br|it|au|nl|my|uk|fr|it|sg|tw|be|nz)$/i;
	host_match = host_pat.test(email);
    }

    if (! host_match) {
	host_pat = /(\@)(ms.+)\.hinet\.(net)$/i;
	host_match = host_pat.test(email);
    }

    if (! host_match) {
	host_pat = /(\@)(m.+)\.url\.com\.(tw)$/i;
	host_match = host_pat.test(email);
    }
    
    if (! host_match) {
	host_pat = /(\@)(coolgoose|software-architecture|hotmail|aspectgroup|bxnet|demon|excibir|freeserve|ihug|ivybridge|lib1|lycos|mailbox|mighty|mcgovern|knowledgefocus|tiscali|unitel|zonnet|gladstone|yahoo)\.co\.(br|za|it|in|jp|au|nl|nz|uk|fr|it|tw|be|kr)$/i;
	host_match = host_pat.test(email);
    }
    if (! host_match) {
	host_pat = /(\@)(mailbox|documentaal|inbox|2e2|arexera|noos|camping-zentrale|bite|bigfoot|bluewin|businessinteractif|comfact|comfactory|cyberus|davide|eol|libero|cyberhouse|liberto|mail|gmx|free|freenet|email|euronet|ngs|fastmail|freemail|interfour|interware|wanadoo|magma|vedeotron|netcologne|netcabo|hookem|vodafoneomnitel|oktad|ptt|pandora|planet|ripley|real|reference|snafu|shaw|mirabeau|club-internet|skynet|swissonline|mos|fh-konzept|hybris|oninet|sympatico|terra|tin|leadingtech|t-online|vipbusiness|virgilio|xnet|yahoo|w3design|w3solutions|worldonline|web)\.(it|at|dk|ca|ch|au|biz|hu|nl|uk|fr|fm|it|tw|be|de|es|cz|yu|md|ro|ru|pl|se|pt|us|cl|gg)$/i;
	host_match = host_pat.test(email);
    }
    if (! host_match) {
	host_pat = /(schal2\@ms\.uky\.edu)$/i;
	host_match = host_pat.test(email);
    }

    var dom_pat = /\.(name|info)$/i;
    var dom_match = dom_pat.test(email);

    if (host_match || dom_match) { return ('valid'); }
    return ('non-anonymous email address');
}


//
// validate_evil_email - verify that an email address is from a competitor.
//
// Returns: a string; "valid" means a good address; otherwise it's an error msg.
//
// This is a basic set of competitor address patterns.  It is by no
// means complete.  There is a corresponding "is_competitor_email()" function
// in the SiteMgr::CgiApp class with the same patterns (not SiteDev, SiteMgr).
//
function SiteJs_IvProc_validate_evil_email(email)
{
    var evil_list = new Array(
	'agari', 'allaire', 'appliedsemantics', 'aptrix', 'atomz',
	'boxcarsoftware', 'broadvision', 'centerrun',
	'clearforest', 'connecti', 'day', 'divine', 'documentum', 'ebt',
	'egrail', 'em3', 'emc', 'epicentric', 'eprise', 'fatwire', 'filenet',
	'filnet', 'gauss', 'gofilnet', 'groovecs', 'hylandsoftware',
	'inso', 'interleaf', 'intranetsolutions', 'inxight', 'kintana',
	'macromedia', 'marimba', 'mediasurface', 'merant',
	'mks', 'mohomine', 'ncompass', 'netobjects', 'nstein',
	'octave', 'openmarket', 'opentext', 'percussion', 'presence',
	'purpleyogi', 'quiver', 'rational', 'readware', 'reddot',
	'semio', 'starbase', 'stellent', 'stratify', 'towersoftware',
	'verinon', 'verity', 'viant', 'vignette', 'worldweb'
    );

    var evil_pat_parts = evil_list.join('|');
    var evil_pat_str = '(\\@|\\.)(' + evil_pat_parts + ')\\.';
    var host_pat = new RegExp(evil_pat_str, 'i');
    var dom_pat = /\.(name|info)$/i;

    if (host_pat.test(email)) { return ('valid'); }
    return ('friendly email address');
}


//
// validate_evil_email - verify that an email address is from a competitor.
//
// Returns: a string; "valid" means a good address; otherwise it's an error msg.
//
// This is a basic set of competitor address patterns.  It is by no
// means complete.  There is a corresponding "is_competitor_email()" function
// in the SiteMgr::CgiApp class with the same patterns (not SiteDev, SiteMgr).
//
/*-----
function ORIG_SiteJs_IvProc_validate_evil_email(email)
{
    var host_pat = /(\@|\.)(atomz|broadvision|ceyoniq|day|documentum|divine|filenet|merant|starphire|stellent|vignette)\./i;
    var dom_pat = /\.(name|info)$/i;

    if (host_pat.test(email)) { return ('valid'); }
    return ('friendly email address');
}
-----*/


//
// validate_length - verify that a string is not too big or too small.
//
// Returns: a string; "valid" means good; otherwise it's an error message.
//
function SiteJs_IvProc_validate_length(str, min, max)
{
    var len = str.length;
    if (min > 0 && len < min) {
	return ('input is too short (atleast ' + min + ' chars needed)');
    }
    else if (max > 0 && len > max) {
	return ('input is too long (' + max + ' chars max)');
    }
    else {
	return ('valid');
    }
}


//
// validate_float - verify that a value is a float.
//
// Returns: a string; "valid" means a good value; otherwise it's an error msg.
//
// Note that using parseFloat() is another option, and check for a good
// return value using isNaN() (see page 264 of rhino book).
//
function SiteJs_IvProc_validate_float(val)
{
    // Perform some simple sanity checks with clear error messages.
    if (val == "") {
	return ('value is blank - expecting a number.');
    }
    else if (val.indexOf(' ') >= 0) {
	return ('value has a space in it - expecting only digits.');
    }
    else if (val.indexOf(',') >= 0) {
	return ('value has a comma "," in it - expecting only digits.');
    }

    // Perform a more thorough pattern-match but a generic error message.
    if (! this.is_float(val)) { return ('value is not a number.'); }

    // Indicate a positive result.
    return ('valid');
}


//
// validate_int - verify that a value is an integer.
//
// Returns: a string; "valid" means a good value; otherwise it's an error msg.
//
// Note that using parseInt() is another option, and check for a good
// return value using isNaN() (see page 264 of rhino book).
//
function SiteJs_IvProc_validate_int(val)
{
    // Perform some simple sanity checks with clear error messages.
    if (val == "") {
	return ('value is blank - expecting a number.');
    }
    else if (val.indexOf(' ') >= 0) {
	return ('value has a space in it - expecting only digits.');
    }
    else if (val.indexOf(".") >= 0) {
	return ('value has a period in it - should be an integer number.');
    }
    else if (val.indexOf(',') >= 0) {
	return ('value has a comma "," in it - expecting only digits.');
    }

    // Perform a more thorough pattern-match but a generic error message.
    if (! this.is_int(val)) { return ('value is not a number.'); }

    // Indicate a positive result.
    return ('valid');
}


//
// validate_csv - make sure a string is non-blank and contains no comma.
//
// This makes sure that a string is okay to be used in a
// comma-separated-value record.
//
// Returns: a string "valid" means good; otherwise it's an error msg.
//
function SiteJs_IvProc_validate_csv(str)
{
    if (this.is_blank(str)) {
	return ('value is blank.');
    }
    else if (str.indexOf(',') >= 0) {
	return ('value has a comma in it (not allowed).');
    }
    else {
	return ('valid');
    }
}


//-- isa ops -----------------------------------------------------------------
//----------------------------------------------------------------------------
//
// is_float - test to see if a string is a valid floating point number.
//
// Returns: true if it is a valid float; false otherwise.
//
// XXX: it turns out that parseFloat() is kind of a sucky mechanism to
// validate syntax.  For example, the following is valid using parseFloat():
// "1250.95 samolians"  - i.e. it groks first number fine, and just tosses
// the rest.  However, the CGI receiver on the server side then has to
// grok it's ass off too, which voids the benefit of client-side checking.
//
function SiteJs_IvProc_is_float(str)
{
    var v = parseFloat(str);
    if (isNaN(v)) { return (false); }
    // xxx: <insert other syntax checks here> (when have time of course)
    return (true);
}


//
// is_int - test to see if a string contains all digits.
//
// Returns: true if it is a valid integer; false otherwise.
//
function SiteJs_IvProc_is_int(str)
{
    var pat = /^\-?\d+$/;
    return (pat.test(str));
}


//
// is_blank - test to see if a string contains all white space.
//
// Returns: true if it is blank; false otherwise.
//
function SiteJs_IvProc_is_blank(str)
{
    var pat = /^\s*$/;
    return (pat.test(str));
}


//-- error log ops -----------------------------------------------------------
// The error log is just a simple array of error messages.
//----------------------------------------------------------------------------
//
// elog_count - query the size of the error log.
//
function SiteJs_IvProc_elog_count()
{
    return (this.elog.length);
}


//
// elog_clear - remove all messages from the error log.
//
function SiteJs_IvProc_elog_clear()
{
    this.elog = new Array();
}


//
// elog_prepend - add an error to the beginning of the error log.
//
function SiteJs_IvProc_elog_prepend(msg)
{
    this.elog.unshift(msg);
}


//
// elog_append - add an error to the end of the error log.
//
// XXXBUG: The John Bug
// XXXBUG: this was hacked to get around the problem jdoumani's browser had.
// XXXBUG: instead of using elog.push() it looks up the length and stores it.
//
function SiteJs_IvProc_elog_append(msg)
{
    //this.elog.push(msg);
    this.elog[this.elog.length] = msg;
}


//
// elog_alert - display the error log in an alert dialog.
//
function SiteJs_IvProc_elog_alert(prefix, suffix)
{
    alert(this.elog_compose(prefix, suffix));
}


//
// elog_compose - formulate the error log into a big string.
//
function SiteJs_IvProc_elog_compose(prefix, suffix)
{
    if (prefix == 'default') {
	prefix = (''
	    + "---------------------------------------------\n"
	    + "Please fix the following in your input:\n"
	    + "---------------------------------------------\n\n"
	);
    }

    if (suffix == 'default') { suffix = ''; }

    return (prefix + this.elog.join("\n\n") + suffix);
}


//-- debug utils -------------------------------------------------------------
//----------------------------------------------------------------------------
//
// dump_props - find out what's in an object.
//
function SiteJs_IvProc_dump_props(obj)
{
    var win = window.open();
    var doc = win.document;
    var prop;

    doc.open('text/plain');
    for (prop in obj) { doc.write(prop + ' => ' + obj[prop] + "\n"); }
    doc.close();
}


//-- misc utils --------------------------------------------------------------
//----------------------------------------------------------------------------
//
// get_value - query the value of an interface element.
//
// This is a bit odd because we are presuming we can express the return
// value of any UI element as a flattened string.  It's not the worst
// assumption but it is an assumption.
//
function SiteJs_IvProc_get_value(elem)
{
    var type = elem.type;

    if (type == 'text') {
	return (elem.value);
    }
    else if (type == 'textarea') {
	return (elem.value);
    }
    else if (type == 'hidden') {
	return (elem.value);
    }
    else if (type == 'select-one') {
	var index = elem.selectedIndex;		// -1 if nothing selected
	if (index >= 0) { return (elem.options[index].value); }
	else { return (''); }
    }
    else if (type == 'select-multiple') {
	return (this.poll_option_menu(elem, '|'));
    }
    else if (elem.length > 1) {
	return (this.poll_btn_group(elem, '|'));
    }
    else if (type == 'checkbox') {
	return (elem.value);
    }
    else {
	return ('');
    }
}


//
// get_desc - figure out the description of an element for reporting purposes.
//
// We allow the form designer to provide a custom string for error reporting
// because in some cases the actual element name may be cryptic.  The
// attribute to use is "iv-desc".  If this attribute is not set then we use
// the element's given name.
//
// If the item description exists, it will prefix the standard description
// enclosed in parentheses.
//
// Like: (Item 35) User Email   is blank.
//       ^^^^^^^^^^^^^^^^^^^^   ^^^^^^^^^
//       overall description    error message
//
function SiteJs_IvProc_get_desc(elem)
{
    var desc = elem[_iv_desc];
    var item = elem[_iv_item];
    var istr = '(Item ' + item + ')';

    // A description of %i implies just use the item description.
    if (desc == '%i') { return (istr); }

    if (desc == null || desc == '') {
	desc = elem.name;
	if (desc == null || desc == '') { desc = 'Unknown Field'; }
    }

    // Add in the item description if it's there (for reference).
    if (item && item != 'undefined') { desc = istr + ' ' + desc; }

    return (desc);
}


//
// poll_btn_group - query the value of a set of buttons.
//
// This should work for both radio and checkbox button groups.
//
function SiteJs_IvProc_poll_btn_group(elem, sep_char)
{
    var size = elem.length;
    var values = Array();
    var i;

    for (i = 0; i < size; ++i) {
	// XXXBUG: The John Bug
	//if (elem[i].checked) { values.push(elem[i].value); }
	if (elem[i].checked) { values[values.length] = elem[i].value; }
    }

    var value = values.join(sep_char);
    return (value);
}


//
// OLD_poll_radio_group - query the value of a set of radio buttons.
//
function OLD_SiteJs_IvProc_poll_radio_group(rg)
{
    var size = rg.length;
    for (var i=0; i<size; ++i) { if (rg[i].checked) { return (rg[i].value); } }
    return ('');
}


//
// poll_option_menu - query the value of an option menu.
//
// This is necessary mainly for multi-pick select lists.
//
function SiteJs_IvProc_poll_option_menu(menu, sep_char)
{
    var options = menu.options;
    var num_options = options.length;
    var values = Array();
    var i;

    for (i=0; i<num_options; ++i) {
	// XXXBUG: The John Bug
	//if (options[i].selected) { values.push(options[i].value); }
	if (options[i].selected) { values[values.length] = options[i].value; }
    }
    var value = values.join(sep_char);
    return (value);
}


//
// trim  - chop leading and trailing whitespace from a string.
// ltrim - chop leading whitespace from a string ("left trim").
// rtrim - chop trailing whitespace from a string ("right trim").
//
function SiteJs_IvProc_trim(str)
{
    str = str.replace(/^\s*/, '');
    str = str.replace(/\s*$/, '');
    return (str);
}
function SiteJs_IvProc_ltrim(str)
{
    return (str.replace(/^\s*/, ''))
}
function SiteJs_IvProc_rtrim(str)
{
    return (str.replace(/\s*$/, ''));
}

//****************************************************************************

