/***********************************************************************************************************/
/***********************************************************************************************************/
// FIELD-RELATED OBJECT and FUNCTIONS (for onscreen fields)
// Copyright 2007-2008 Collaborative Bike Map Project
/***********************************************************************************************************/
/***********************************************************************************************************/
// Field types...
var FT_TEXTBOX = 1;
var FT_TEXTAREA = 2;
var FT_COMBO = 3;
var FT_COMBO_OPT = 4;
var FT_DIRLABEL = 5;
var FT_CT = 6;
var FT_INDEX = 7;
var FT_DIRDIV = 8;

// Special value types...
var VT_INT = 1;     // integer (non-negative)
var VT_DEC = 2;     // decimal

/* REMOVED...
// "di" member and similar values
var FORE = 1;
var BACK = 2;
...REMOVED */

// Initial value for "unknown" in combo boxes
// var UNK = 20;      // Moved to model.js

// Check/save times
// var TIME_TYPE = 1;   // Check when typing or (in case of combo box) when item is selected
var TIME_CHNG = 2;   // Check when route is saved
var TIME_NEXT = 3;   // Check when a different record is selected
var TIME_DONE = 4;   // Check when route is saved

// Failure reasons...
var FAIL_MISSING = 1;
var FAIL_LONG = 2;
var FAIL_INT = 3;
var FAIL_NUM = 4;
var FAIL_EXTRA = 5;

// Record types (object types)...
var RTE = 1;
var SEG = 2;
var PNT = 3;

// Globals
// var DIRDIV_visible = {oppseg:false, opppnt:false};       // state of oppseg div
var FldInfo = new FieldInfo;

/***********************************************************************************************************
// Fields class containing info about fields on the form.  Used only within FieldInfo singleton object.
// NOTE: Properties for each field in Fields object are described in FieldInfo constructor.
************************************************************************************************************/
// Constructor/template
function Fields() {
	// ONLY THESE CHECK/SAVE POSSIBILITIES ARE ALLOWED:
	//     - Combo boxes - Check & save on change (TIME_CHNG) and on next (TIME_NEXT) just in case; 
	//       user can't enter non-listed value; 
	//       May require special processing if arrow keys used to change the value 
	//       (because on-change event not triggered right away in that case)
	//     - Text boxes - Check and save on TIME_NEXT; Length limits are handled in HTML
	//     - Text areas - Check length on typing regardless; check in full and save on TIME_NEXT;
	//       If paste performed, don't flag excess length until subsequent typing.
	//     - Show/hide buttons (crt/del) - Save on click event (TIME_CHNG) and on next (TIME_NEXT).
	//       Also, clicking button is like a TIME_CHNG event for directional fields.
	//
	// For route-level fields, finishing route is equivalent of "next".
	// But for now, code also handles TIME_NEXT for either of these field types.
	// Also sync all editable fields with memory model structure on init.
	// Also check combinations of fields when next record is selected.
	// WARNING: Don't allow duplicate "me" elements within this object! (so we can set up a reverse map)
	//
	// Editable route fields...
	this.routename = {rt:RTE, iv:"", tp:FT_TEXTBOX,  ed:true, me:"name", ln:200, rq:true, nm:"route 'Name'"};
	// Non-editable route fields...
	this.routelen  = {rt:RTE, iv:"" , tp:FT_TEXTBOX,   ed:false, me:"len"};
	this.routebzid = {rt:RTE, iv:"" , tp:FT_TEXTBOX,   ed:false, me:"bzid"};

	// Editable segment fields...
	this.segname =  {rt:SEG,  iv:"", tp:FT_TEXTBOX,  ed:true, me:"name", ln:100, rq:false, nm:"segment 'Name'"};   // Hide for now -- not used
	this.segdesc =  {rt:SEG,  iv:"", tp:FT_TEXTAREA,  ed:true, me:"desc", ln:5, rq:false, nm:"segment 'Description'"};
	this.numlanes = {rt:SEG,  iv:"", tp:FT_TEXTBOX,  ed:true, me:"numlanes", ln:30, rq:false, nm:"'# lanes each way'"};
	this.speed =    {rt:SEG,  iv:"", tp:FT_TEXTBOX,  ed:true, me:"speed", vt:VT_INT, ln:7, rq:false, nm:"'Speed limit'", ckfn:check_speed};
	this.zoom =     {rt:SEG,  iv:ZOOM_MIN,    tp:FT_COMBO, ed:true, me:"zoom", ln:null, rq:true, nm:"'Zoom level'"};
	this.oneway =   {rt:SEG,  iv:OW_NO,       tp:FT_COMBO, ed:true, me:"dir", ln:10, rq:true, nm:"'One-way'"};
	this.typedes =  {rt:SEG,  iv:BWTYPE_NONE, tp:FT_COMBO, ed:true, me:"bikeway", ln:null, rq:true, nm:"'Bikeway Type'"};  // REMOVED: di:FORE
	this.roadtype = {rt:SEG,  iv:UNK,  tp:FT_COMBO, ed:true, me:"roadtype", ln:null, rq:true, nm:"'Road detail'"};				// REMOVED: di:FORE
	this.skill =    {rt:SEG,  iv:UNK,  tp:FT_COMBO, ed:true, me:"skill", ln:null, rq:true, nm:"'Road skill level'"};			// REMOVED: di:FORE
	this.sidewalk = {rt:SEG,  iv:UNK,  tp:FT_COMBO, ed:true, me:"sidewalk", ln:null, rq:true, nm:"'Sidewalk'"};
	// Non-editable segment fields...
	this.seglen =   {rt:SEG,  iv:"", tp:FT_TEXTBOX,  ed:false, me:"len"};
	this.segbzid =  {rt:SEG,  iv:"", tp:FT_TEXTBOX,  ed:false, me:"bzid"};
	// Editable point fields...
	this.isnotepnt ={rt:PNT,  iv:OW_NO, tp:FT_COMBO,    ed:true, me:"isnotepnt", ln:null, rq:true,  nm:"'Comment point'"};
	this.notecat =  {rt:PNT,  iv:"",    tp:FT_COMBO,    ed:true, me:"notecat",   ln:null, rq:false, nm:"'Comment category'"};
	this.pntnote =  {rt:PNT,  iv:"",    tp:FT_TEXTAREA, ed:true, me:"pntnote",   ln:500,  rq:false, nm:"point 'Comment'"};
	// Non-editable point fields...
	this.lat =      {rt:PNT,  iv:"", tp:FT_TEXTBOX,  ed:false, me:"lat"};
	this.lng =      {rt:PNT,  iv:"", tp:FT_TEXTBOX,  ed:false, me:"lng"};
	this.pntbzid =  {rt:PNT,  iv:"", tp:FT_TEXTBOX,  ed:false, me:"bzid"};
	this.bndpnt =   {rt:PNT,  iv:FLD_NO, tp:FT_COMBO, ed:false, me:null};
	this.intpt =    {rt:PNT,  iv:FLD_NO, tp:FT_COMBO, ed:false, me:null};
	
	//////////////////////////
	// NOTE: If any fields are not a one-to-one mapping to memory model fields (for example, if zoom level and hide flag are combined) then
	// the edit field will have to have no mapping to a particular model field indicated above.  Instead logic to udpate the memory model  
	// based on the field will have to be triggered by the field change event, and logic to set the on-screen field will have to be 
	// attached to the code that loads the fields.
	//////////////////////////
	
	// Removed stuff...
	// REMOVED: this.opp_roadtype={rt:SEG,iv:null,  tp:FT_COMBO, ed:true, me:"opp_roadtype", ln:null, rq:true, nm:"'Backward road type'", di:BACK};
	// REMOVED: this.opp_skill ={rt:SEG,  iv:null,  tp:FT_COMBO, ed:true, me:"opp_skill", ln:null, rq:true, nm:"'Backward road skill level'", di:BACK};
	// this.pathtype = {rt:SEG,  iv:UNK,  tp:FT_COMBO, ed:true, me:"pathtype", ln:null, rq:true, nm:"Path type'"};
	// REMOVED: this.roadtype_dir = {rt:SEG, iv:"", tp:FT_DIRLABEL, ed:false, me:null};
	// REMOVED: this.skill_dir =    {rt:SEG, iv:"", tp:FT_DIRLABEL, ed:false, me:null};
	// Save for maybe later...
	// Combo box entries... SAVE FOR LATER because only needed if EB, WB, etc. used instead of "forward" and "backward"
	// this.oneway_opt_fore = {rt:SEG, iv:"Fore", tp:FT_COMBO_OPT, ed:false, me:null};
	// this.oneway_opt_back = {rt:SEG, iv:"Back", tp:FT_COMBO_OPT, ed:false, me:null};
}

/***********************************************************************************************************
// FieldInfo class representing form fields and related control data
// Used only for singleton object.
// This class contains an instance of the Fields object and uses it freely.
************************************************************************************************************/
// Constructor/template
function FieldInfo() {
	var fldname, propname;
	var elmt;

	//****************************************
	// Data Members
	//****************************************
	this.fields = new Fields;            // properties of each field
	this.reversefields = new Object;     // reverse mapping of memory model members (.me) back to screen fields
	this.fieldelements = [	             // list of all field properties used in Fields object
		"rt",  // rectype type: RTE, SEG or PNT
		"iv",  // initial value - For combo boxes, null means use first entry (TO DO: If there is a null entry, use it instead)
		"tp",  // type of field - FT_COMBO, FT_TEXTBOX, FT_TEXTAREA, FT_DIRLABEL, FT_CT
		"ed",  // whether user-editable
		// "ct" and "st" REMOVED BECAUSE CHECK & SAVE TIMES NOW BASED ENTIRELY ON FIELD TYPE (tp)
		"me",  // member name in memory model data structure (route.seglist, route.pointlist)
		"ln",  // max # of characters allowed (user-editable fields only)
		"rq",  // whether field is required (user-editable fields only)
		"vt",  // special value type - VT_INT, VT_DEC, etc. (free text is default) (user-editable fields only)
		"nm",  // displayable name of field; 
		       // NOT SUPPORTED (REMOVED): $$ will be replaced with "forward" for forward bidirectional fields 
		       //   when backward partner is also displayed, otherwise replaced with ""
		// REMOVED: "di",  // direction of bidirectional field - FORE or BACK 
		"ckfn"];    // extra check function to be applied to field (user-editable fields only) - 
					// Function must take two arguments: fldname and fldval, though it may examine other fields on the form too
					// This function is called after other tests are performed.  It returns true if
					// extra test passes, false if it doesn't.  If the function returns true but previous tests fail,
					// field still fails.  It displays its own error messages.
					
	//****************************************
	// Constructor code
	//****************************************
	// Fill in undefined elements with null, setup reversefields
	for (fldname in this.fields) {
		// Fill in undefined elements
		for (propname in this.fieldelements) {        // loop through field properties
			if (this.fields[fldname][propname] == undefined) {    // change undefined to null
				this.fields[fldname][propname] = null;
			}
			/* // Check that ct is supported at this time
			if (this.fields[fldname].ct == TIME_DONE && 
				  (this.fields[fldname].rt == SEG ||  this.fields[fldname].rt == PNT)) 
				alert ("Unsupported ct value (TIME_DONE) for field " + fldname + ".");  */
		}
		// Fill in reversefields, which maps each memory model structure field back to a screen field
		if (this.fields[fldname].me != null) {
			this.reversefields[this.fields[fldname].me] = fldname;
		}
	}

	//fldname = "segname";
	//elmt = document.getElementById (fldname);			// get HTML element
	//elmt.onchange = this.checkOnClick;
	
}

/*******************************************************************
// Disable and reset to initial values all on-screen fields matching the input rectype.
// Disable means not just make read-only but also gray out and make unselectable.
// This is normally done when no record of the corresponding type is selected at all.
// It doesn't disable constituent types, e.g. if called for SEG it doesn't disable
// point fields.  Caller must do this (and probably always should).
// INPUTS: rectype - RTE, SEG or PNT. 
*******************************************************************/
FieldInfo.prototype.disableFields = function (rectype) {
	var fldname, elmt;

	// Reset all fields with matching rectype to initial values
	for (fldname in this.fields) {
		if (this.fields[fldname].rt == rectype) {         // if appropriate rectype
			// if directional label (part of forward field label that changes when reverse info is displayed)
			if (this.fields[fldname].tp == FT_DIRLABEL) {       
				elmt = document.getElementById (fldname);			// get HTML element
				elmt.innerHTML = this.fields[fldname].iv;      // change label on the screen
			}
			else 
				this.disableField (fldname, this.fields[fldname].iv);			
		}
	}

	// Disable and zero out counts and counters
	if (rectype == SEG) {
		this.disableField ("segnum", 0);
		this.disableField ("segct", 0);
	}
	else if (rectype == PNT) {
		this.disableField ("pntnum", 0);
		this.disableField ("pntct", 0);
	}
	
	// Disable field-related buttons, hide directional div
	/* REMOVED...
	if (rectype == SEG) {
		var ar = ["backinfocrtbtn", "backinfodelbtn"];
		for (i in ar) {
			document.getElementById(ar[i]).disabled = "disabled";
		}
		// Hide opposite div
		this.showHideDirDiv (false, false);
	}
	else 
	...END REMOVED */

}

/*******************************************************************
// Disable an on-screen field and set it to the optional input value 
// (presumably an initial or empty value). If no input value is provided,
// do not update the field contents.
// This is done when no record of the corresponding type is selected at all.
*******************************************************************/
FieldInfo.prototype.disableField = function (fieldname, newval) {
	var elmt = document.getElementById (fieldname);
	if (elmt == undefined || elmt == null) {
		alert ("Undefined element: " + fieldname);
	}
	if (newval != undefined)
		elmt.value = newval;
	elmt.disabled = "disabled";
	removeClass (elmt, "editbox");
	removeClass (elmt, "lockedbox");
	addClass (elmt, "inertbox");
}

/*******************************************************************
// Enable on-screen fields matching the input rectype.
// Also enable field area buttons such as backinfocrtbtn.
// Fields become enabled and selectable but non-editable fields 
// will still be read-only.
// It doesn't enable constituent types, e.g. if called for SEG it doesn't enable
// point fields.  Caller must do this.
// INPUTS: rectype - RTE, SEG or PNT. 
*******************************************************************/
FieldInfo.prototype.enableFields = function (rectype) {
	var fldname, elmt;

	// Enable all fields with matching rectype
	for (fldname in this.fields) {
		if (this.fields[fldname].rt == rectype) {         // if appropriate rectype
			/* // if directional label (part of forward field label that changes when reverse info is displayed)
			if (this.fields[fldname].tp == FT_DIRLABEL) {       
				elmt = document.getElementById (fldname);			// get HTML element
				elmt.innerHTML = this.fields[fldname].iv;      // change label on the screen
			} else {   */
			// Reset field, enable as editable or read-only based on .ed property
			this.enableField (fldname, this.fields[fldname].ed);   // this.fields[fldname].iv);					
		}
	}

	// Enable counts and counters and field buttons
	if (rectype == SEG) {
		// Enable segment fields
		this.enableField ("segnum", true);     // editable
		this.enableField ("segct", false);     // read-only
		// Enable segment buttons
		// REMOVED: document.getElementById("backinfocrtbtn").disabled = "";
		// REMOVED: document.getElementById("backinfodelbtn").disabled = "";
	}
	else if (rectype == PNT) {
		// Enable point fields
		this.enableField ("pntnum", true);     // editable
		this.enableField ("pntct", false);     // read-only
		// Enable point buttons
	}	
}

/*******************************************************************
// Enable an on-screen field and change its style to editable or 
// read-only based on the input editable flag.
// Optionally set the field to the specified input value (legacy);
// otherwise do not update the field contents.
*******************************************************************/
FieldInfo.prototype.enableField = function (fieldname, editable, newval) {
	
	var elmt = document.getElementById (fieldname);
	if (newval != undefined)
		elmt.value = newval;

	removeClass (elmt, "inertbox");
	elmt.disabled = "";
	
	if (editable) {
		removeClass (elmt, "lockedbox");
		addClass (elmt, "editbox");
	}
	else {
		removeClass (elmt, "editbox");
		addClass (elmt, "lockedbox");
	}
}

/*******************************************************************
// Set and enable/disable screen fields for empty route case,
// where the route is open and editable but it has no segments or points.
*******************************************************************/
FieldInfo.prototype.configFields_NewEmptyRoute = function () {
	// Enable route fields
	this.enableFields (RTE);
	// Disable and empty segment and point fields
	this.disableFields (SEG);     
	this.disableFields (PNT);     
}

/*******************************************************************
// Set and enable/disable screen fields for empty route case,
// where the route is open and editable but it has no segments or points.
*******************************************************************/
FieldInfo.prototype.configFields_FirstRoutePoint = function () {
	// Enable all fields now
	// this.enableFields (RTE);  // should already be enabled, but do this to support future code changes   
	this.enableFields (SEG);     
	this.enableFields (PNT);     
}

/*******************************************************************
// Set and enable/disable screen fields for no selected route case,
// where all route, segment and point fields are empty and disabled.
*******************************************************************/
FieldInfo.prototype.configFields_NoRoute = function () {
	this.disableFields (RTE);
	this.disableFields (SEG);   
	this.disableFields (PNT);
}

/*******************************************************************
// Set counts and counters for the current route for both segments and points
// based on memory model.  This does not enable or disable the fields.
*******************************************************************/
FieldInfo.prototype.setNumbers = function () {
	this.setPntNumbers ();
	this.setSegNumbers ();
}

/*******************************************************************
// Update segnum and segct fields for currently selected segment.
// Need to do when segments inserted, deleted, etc. 
*******************************************************************/
FieldInfo.prototype.setSegNumbers = function () {
	// Display index & count fields
	var rte = SelectedSegment.route;
	var elmt1 = document.getElementById ("segct");
	elmt1.value = rte.seglist.length;
	var elmt2 = document.getElementById ("segnum");
	elmt2.value = rte.seglist.getIndex (SelectedSegment) + 1;      // adjust to 1-based
}

/*******************************************************************
// Update pntnum and pntct fields for currently selected point.
// Need to do when points inserted, deleted, etc. 
*******************************************************************/
FieldInfo.prototype.setPntNumbers = function () {
	// Display index & count fields		
	var rte = SelectedPoint.route;
	var elmt1 = document.getElementById ("pntct");
	elmt1.value = rte.pointlist.length;
	var elmt2 = document.getElementById ("pntnum");
	elmt2.value = rte.pointlist.getIndex (SelectedPoint) + 1;      // adjust to 1-based
}

/*******************************************************************
// Enable and reset screen fields for new point and/or segment case.
// If newseg = true, update segment information, otherwise leave it as-is.
// If newpnt = true, update point information, otherwise leave it as-is.
*******************************************************************/
/* FieldInfo.prototype.setFields_NewPointOrSegment = function (newpnt, newseg) {
	// Optionally set point fields to initial values	
	if (newpnt)
		this.enableAndInitFields (PNT);        
	// Optionally set segment fields to initial values
	if (newseg)               
		this.enableAndInitFields (SEG);   
	// Don't do anything with route fields
	// Set counts and counters	
	this.setCounters ();
}  */
/*******************************************************************
// Reset all fields on the screen to default values used 
// when segment or point (per rectype) is first created.
// NOT USED -- DELETE!!!
*******************************************************************/
/* FieldInfo.prototype.resetAll = function (rectype, lat, lng) {
	var fldname, elmt;

	// Reset all fields on the screen to initial values
	for (fldname in this.fields) {
		elmt = document.getElementById (fldname);			// get HTML element
		elmt.value = this.fields[fldname].iv;               // reset to initial value (iv)
	}

	// Hide opposite div
	this.showHideDirDiv (false, false);
}  */


/*******************************************************************
// Check all on-screen saveable fields of the specified
// entity (route, segment or point) that are to be checked now
// (based on actiontime).  Do no saving here.
// Input: rectype - Set to RTE, SEG or PNT to indicate which entity
//                  whose fields should be checked.
//        actiontime - May be TIME_NEXT or TIME_CHNG for this routine;
//                     for other times, this routine does nothing.
// Return: True if fields are valid, false otherwise
*******************************************************************/
FieldInfo.prototype.checkFields = function (rectype, actiontime) {
	var fld;
	var rc = true;	
	var checkit;

	if (actiontime == TIME_CHNG || actiontime == TIME_NEXT) {     // check input actiontime
		// Check fields
		for (fld in this.fields) {
			checkit = ((actiontime == TIME_CHNG && this.fields[fld].tp == FT_COMBO) || 
					   (actiontime == TIME_NEXT && this.fields[fld].tp != FT_COMBO));
			// if field has matching rectype and should be checked now
			if (this.fields[fld].rt == rectype && checkit) {
				if (!this.checkField (fld)) {
					rc = false;
					break;     // exit for loop
				}
			}
		}
	}

	// NOTE: No segment or point processing for actiontime = TIME_DONE is performed
	return rc;
}

/*******************************************************************
// Check specified field for valid input.  Displays failure message if any.
// It won't check any fields that are not editable or 
// aren't visible (because they're in a hidden reverse div).
*******************************************************************/
FieldInfo.prototype.checkField = function (fldname) {
	var fldentry;
	var elmt, fldval;
	var fld_empty;
	var nm, msg;
	var err;
	var ok = true;

	// Assume field exists
	// if (this.fields[fldname] != undefined) {      // if field exists

	// Check only if field is editable
	fldentry = this.fields[fldname];
	if (fldentry.ed) {    
	    // REMOVED: && (fldentry.di != BACK || (fldentry.di == BACK && divShown ("oppseg")))) {

		// Make programmed checks...
		elmt = document.getElementById (fldname);     // get HTML element
		fldval = trim (elmt.value);                   // get trimmed field value from form
		fld_empty = (fldval.length == 0);			  // note if trimmed field is empty

		// if field is empty, fail if required field, otherwise pass
		if (fld_empty) {                    // if field is empty			
			if (fldentry.rq) {              // if required field, fail						
				ok = false;                 // FAIL
				err = FAIL_MISSING;         
			}
			// else not required field - pass
		}

		// else field is not empty
		else {
			if (fldentry.vt == VT_INT) {     // if non-negative integer
				if (!isInteger (fldval)) {           // check
					ok = false;
					err = FAIL_INT;
				}
			}
			else if (fldentry.vt == VT_DEC) {             // if decimal number
				if (!isNumber (fldval)) {                 // check 
					ok = false;
					err = FAIL_NUM;
				}
			}
			if (fldentry.ln != null && fldentry.ln > 0) {      // if max length defined for this field
				if (fldval.length > fldentry.ln) {       // check against max length
					ok = false;
					err = FAIL_LEN;
				}
			}
		}

		// Also call special test function for this field, if function is defined  
		if (ok && fldentry.ckfn != null) {      // if test function is defined, call it...
			ok = fldentry.ckfn (fldname, fldval);     // Function may check anything on the form
			if (!ok)
				err = FAIL_EXTRA;
		}
	}

	// Display error message if tests failed
	if (!ok) {
		/*   REMOVED...
		// Replace $$ if any
		if (fldentry.nm.di == FORE && fldentry.nm.search (/$$/) >= 0) {
			if (divshown ("oppseg"))
				nm = fldentry.nm.replace(/\$\$/, "Foreward");    // if opp div shown, insert direction in place of $$
			else   
				nm = fldentry.nm.replace(/\$\$/, "");            // else no direction, so ignore $$
		}
		else  ...END REMOVED */
		
		nm = fldentry.nm;		

		if (err == FAIL_MISSING) 
			msg = "The " + nm + " field is required.";
		else if (err == FAIL_INT)
			msg = "The " + nm + " field must be a non-negative whole number (0, 1, 2, etc.)";
		else if (err == FAIL_NUM)
			msg = "The " + nm + " field must be a valid number.";
		else if (err == FAIL_LEN)
			msg = "The " + nm + " field may be at most " + fldentry.ln + " characters long.";
		else
			msg = "";

		// Display message
		if (msg != "")
			alert (msg);
	}

	// Return final result
	return ok;
}

/*******************************************************************
// Save all on-screen saveable fields of the specified entity (route, segment or point)
// that are to be saved now (based on actiontime).
// Data is saved into the corresponding memory model structure for the current 
// route, segment or point.  No validity checking is performed... should've already
// been done by checkFields().
// Input: rectype - Set to RTE, SEG or PNT to indicate which entity
//        whose fields should be saved.
*******************************************************************/
FieldInfo.prototype.saveFields = function (rectype, actiontime) {
	var fld;

	if (this.getObjByType(rectype) != null) {       // if SelectedSegment and SelectedPoint and OpenRoute aren't null
		for (fld in this.fields) {                     // loop through fields
			if (this.fields[fld].rt == rectype && this.fields[fld].st == actiontime && this.fields[fld].ed) {    
				        // if fields has matching rectype and save time
				 this.saveField (fld);                // save it
			}
		}
		/* REMOVED...
		// Save opposite div status, regardless of actiontime
		if (rectype == SEG) {
			SelectedSegment.use_opp = divShown ("oppseg");	// save opposite div status
		}
		...REMOVED */
	}
}

/*******************************************************************
// Save specified screen field contents into memory model.
// No data checking is performed.
*******************************************************************/
FieldInfo.prototype.saveField = function (fldname) {
	var obj, member, elmt;

	if (this.fields[fldname] != undefined) {      // if field exists  
		obj = this.getObjByType (this.fields[fldname].rt);   // get corresponding data object (current route, segment or point)
		member = sf (this.fields[fldname].me);                    // get corresponding data member name
		if (member != null) {   // if field has a corresponding data member (i.e. if it's saveable/loadable)
			elmt = document.getElementById(fldname);     // get HTML element
			obj[member] = elmt.value;				// save field
		}
	}
}

/*******************************************************************
// Load all on-screen saveable fields of the specified entity (route, segment or point).
// Data is loaded from the corresponding memory model structure for the current 
// route, segment or point.  It assumes fields are already enabled as needed.
// No validity checking is performed.
// Input: rectype - Set to RTE, SEG or PNT to indicate which entity
//        whose fields should be loaded.
*******************************************************************/
FieldInfo.prototype.loadFields = function (rectype) {
	var fld, elmt1, elmt2;

	if (this.getObjByType(rectype) != null) {       // if SelectedSegment and SelectedPoint and OpenRoute aren't null

		// Load fields
		for (fld in this.fields) {                           // loop through fields
			if (this.fields[fld].rt == rectype) {    // if field is of the right type
				 this.loadField (fld);                // load it
			}
		}
		
		/* REMOVED...
		// Load opposite div status 
		if (rectype == SEG) {
			this.showHideDirDiv (SelectedSegment.use_opp, false);		// show or hide div based on oj.use_opp (SelectedSegment.use_opp)
		}
		...REMOVED */
		
		// Display index & count fields
		if (rectype == SEG) 
			this.setSegNumbers();
		else
			this.setPntNumbers();
	}
}

/*******************************************************************
// Load specified on-screen field from memory model structure.
*******************************************************************/
FieldInfo.prototype.loadField = function (fldname) {
	var obj, member, elmt;
	
	if (this.fields[fldname] != undefined) {      // if field exists  
		obj = this.getObjByType (this.fields[fldname].rt);   // get corresponding data object (current route, segment or point)
		member = sf (this.fields[fldname].me);                    // get corresponding data member name
		if (member != null) {			// if field has a corresponding data member (i.e. if it's saveable/loadable)
			elmt = document.getElementById(fldname);     // get HTML element
			elmt.value = nz(obj[member]);					// load field
		}		
	}
}


/*******************************************************************
// Returns the saved value in the memory model structure for
// a given screen name.  Return undefined if no such field is defined.
*******************************************************************/
FieldInfo.prototype.getSavedValue = function (fldname) {
	var obj, member, elmt;
	var outval = undefined;

	if (this.fields[fldname] != undefined) {      // if field exists  
		obj = this.getObjByType (this.fields[fldname].rt);   // get corresponding data object (current route, segment or point)
		member = sf (this.fields[fldname].me);                    // get corresponding data member name
		if (member != null) {					// if field has a corresponding data member (i.e. if it's saveable/loadable)
			outval = obj[member];				// get value from memory object
		}
	}
	
	return outval;
}



/*******************************************************************
// REMOVED, BECAUSE DIRECTIONAL DATA IS NO LONGER SUPPORTED.
// Perform show/hide of directional div.  This makes all 
//    appropriate on-screen changes and, if div is hidden, clears the "opposite"
//    on-screen fields (without changing the data in memory, which will get changed
//    next time fields are saved to memory).
// Inputs: showopp - True to show directional div (containing "opposite" info), 
//                   false to hide it.
//         prompt - Whether to prompt the user before emptying fields (if fields are emptied)
// RULE: Values within opposite div are always null when not shown!!!
// MUST DO THE FOLLOWING UPON HIDE:
//  - Prompt the user (if opposite fields not blank)
// 	- Hide opposite div 
//  - Show/hide toggle button
// 	- Change field labels (fore only)
// 	- Null opposite fields
// MUST DO THE FOLLOWING UPON SHOW:
// 	- Display opposite div
//  - Show/hide toggle button
// 	- Change field labels
*******************************************************************/
/* REMOVED...
FieldInfo.prototype.showHideDirDiv = function (showopp, prompt) {
	var rt, div, shown;
	var fldname, elmt, prompt_user;

	// Only supports "oppseg" div now...
	// If we support other opposite info (for points for example), must add lots of conditional logic
	rt = SEG;      // hardcode
	div = "oppseg";
	shown = divShown (div);

	// hide opposite div...
	if (!showopp) {
		// Check if any fields to be hidden are not blank		
		prompt_user = false
		if (prompt) {
			for (fldname in this.fields) {
				if (this.fields[fldname].ed && this.fields[fldname].di == BACK) {   // if editable field in div we're hiding
					elmt = document.getElementById (fldname);
					if (!isEmpty(elmt.value)) {      // if field has a value 
						prompt_user = true;		     // need to prompt user before deleting!
						break;                       // exit for loop
					}
				}
			}
		}

		// If okay with user to continue
		if (!prompt_user || confirm ("Are you sure?  This will delete the 'backward' info for the segment.")) {
			setStyle (div, "display", "none");        // show hidden div
			for (fldname in this.fields) {
				if (this.fields[fldname].tp == FT_DIRLABEL) {		      // if directional label 
					elmt = document.getElementById (fldname);
					elmt.innerHTML = "";      // set to blank
				}
				else if (this.fields[fldname].di == BACK) {       // normal field within opposite div -- set to initial value
					elmt = document.getElementById (fldname);
					elmt.value = this.fields[fldname].iv;
				}
				// Now display & hide appropriate buttons
				setStyle("backinfocrtbtn", "display", "inline");
				setStyle("backinfodelbtn", "display", "none");
			}
		}
	}	

	// Else show opposite div
	else if (showopp) {
		setStyle (div, "display", "inline");       // display hidden div
		for (fldname in this.fields) {
			if (this.fields[fldname].tp == FT_DIRLABEL) {    // if directional label - set to "forward"
				elmt = document.getElementById (fldname);
				elmt.innerHTML = "Foreward ";
			}
			// Shouldn't initialize fields in opposite div, because hide does it
			// Now display & hide appropriate buttons
			setStyle("backinfodelbtn", "display", "inline");
			setStyle("backinfocrtbtn", "display", "none");
		}
	}
} 
...REMOVED */


/********************************************************************
// Check field if field's checktime property = TIME_CHNG.
// This is only ever used for combo boxes because onchange event can't be 
// cancelled, and also because loss of focus that triggers onchange isn't  
// guaranteed to occur when user leaves the field, making onchange unreliable.
// Even with combo boxes it's possible to use arrow keys to change contents
// and not trigger onchange event in Mozilla... must decide how to deal with that.
// Input: fieldname - Name of field to be checked.
// Return: True if okay, false if field had to be truncated.
*********************************************************************/
FieldInfo.prototype.checkOnChange = function (fieldname) {
	var ok = true;
	if (this.fields[fieldname].tp == FT_COMBO) {
		// Save field contents to memory model (no need to ever check combo boxes)
		savefield (fieldname);
	}
	return ok;
}


/* "TIME_NEXT" PROCESSING
The following logic identifies actions that cause the next segment or next point
to become selected.  This is necessary for determining when to check fields
with checktime = TIME_NEXT.  These fields must also be checked when the route is completed.

Summary:
New segment becomes selected when:
1. Split segment (if new seg becomes current)
2. Click on new segment
3. Advance to new segment using nav buttons
4. Click on new point, so new segment becomes current
5. Advance to new point using nav buttons, so new segment becomes current
6. Delete 2nd to last point of a segment, so new segment becomes current (and prior current segment still exists)
7. Append a new point after splitting segment (new segment pending)
8. Insert a new point before first point of a segment (unless first point of route)

New point becomes selected when:
1. Click on new point
2. Advance to new point using nav buttons
3. Click on new segment, so new point becomes current
4. Click on new segment using nav buttons, so new point becomes current
5. Append or insert a new point

Note: Case where route is finished or saved is not handled here.

Use function called determineNextChecks() -
Looks for these criteria, and returns whether to check Route, Segment, and/or Point info (via temp object).
*/

/********************************************************************
* Check AND SAVE *appropriate* on-screen fields and display error message as appropriate.
* If new current segment, also save current segment fields as residuals for next time we 
* create a segment.  This routine figures out, from the input "situation", 
* whether a different segment and/or point is going to
* become current, thus requiring validation check of the existing on-screen 
* segment and/or point fields respectively.
* It returns a boolean indicating whether fields are okay or not.
* This function should be called at the beginning of event handlers 
* to determine what to check before carrying out
* the handler.  The event handler can then check fields accordingly. 
* Inputs:
*  situation - Corresponds to the event that triggered possible change in
*              which route, segment and/or point is current...
*   - SIT_SPLIT_SEG - Segment split at current point
*   - SIT_SEG_NAV - Segment clicked on map or selected using nav fields or buttons
*   - SIT_PNT_NAV - Point clicked on map or selected using nav fields or buttons
*                   (previously these were distinguished into SIT_***_NAV and SIT_***_CLICK)
*   - SIT_PNT_DEL - Point deleted
*   - SIT_PNT_NEW - Point added in new mode (first point of route)
*   - SIT_PNT_APP - Point added in append mode (to end of route)
*   - SIT_PNT_INS_AFTER - Point inserted after existing point (whether in middle or at end of route)
*   - SIT_PNT_INS_BEFORE - Point inserted before existing point
*   - SIT_PNT_NEW - First point of route is being added
*  new_sel_item - Point or segment clicked on or navigated to (as object) -
*       If situation = SIT_SEG_NAV, set to segment being selected;
*   	If situation = SIT_PNT_NAV, set to point being selected;
*       Other situation values: new_sel_item is not used.
* Returns:
*   True if fields check out, false otherwise.
********************************************************************/
FieldInfo.prototype.checkAndSaveOnNext = function (situation, new_sel_item) {
	var ok;
	//alert ("In checkAndSaveOnNext ok 1 = " + ok);

	// Determine whether to check segment fields or point fields or both
	var recs_to_check = this.determineNextChecks (situation, new_sel_item);
         // see determineNextChecks() for definition of recs_to_check	
	//alert ("In checkAndSaveOnNext ok 2 = " + ok);

	// Execute checks identified above
	ok = this.DoNextChecks (recs_to_check);
	//alert ("In checkAndSaveOnNext ok 3 = " + ok);

	// Check combinations of fields
	ok = ok && this.checkCombinations (recs_to_check);
	//alert ("In checkAndSaveOnNext ok 4 = " + ok);

	// If everything okay, save corresponding fields (per recs_to_check)
	if (ok) {
		if (recs_to_check.check_seg)
			this.saveFields (SEG);
		if (recs_to_check.check_pnt)
			this.saveFields (PNT);
	}
	
	// Also record saved segment values as residual values (for next time we create a segment)
	if (recs_to_check.check_seg)
		SelectedSegment.memorize();
	
	//alert ("In checkAndSaveOnNext ok 5 = " + ok);

	return ok;
}


/*******************************************************************
* Determine whether a different segment and/or point is going to
*   be made current, thus requiring validation of segment and/or point fields with 
*   checktime = TIME_NEXT.  
* Inputs:
*  situation - See checkAndSaveOnNext function.
*  new_sel_item - See checkAndSaveOnNext function.
* Returns:
*  Anonymous object containing the following data members:
*    - check_seg - boolean - True if segment fields must be checked
*    - check_pnt - boolean - True if point fields must be checked
* Note: It's okay to set the output flags for cases where point or segment
*       may have null info, since checks not performed for null info anyway.
* Warning: This routine is dangerously redundant in that it must predict what other code will do
*   as far as changing SelectedSegment, etc.  But it's very convenient to centralize this logic
*   here.
********************************************************************/
FieldInfo.prototype.determineNextChecks = function (situation, new_sel_item) {

	var newseg;
	var outobj = {check_seg:false, check_pnt:false};
	
	switch (situation) {
		case SIT_SPLIT_SEG:         // segment is split or terminated at the current point
		case SIT_PNT_NEW:           // or first point of route is being added
			// splitting doesn't currently change SelectedPoint or SelectedSegment
			// adding first point and segment doesn't either
			break;
			
		case SIT_SEG_NAV:         // new segment is selected by clicking or navigating 
			if (new_sel_item != SelectedSegment) {       // if SelectedSegment will change
				outobj.check_seg = true; 
				// if SelectedPoint isn't first point of newly selected segment (i.e. if SelectedPoint will change)...
				if (SelectedPoint != new_sel_item.firstpoint)    
					outobj.check_pnt = true;
			}	
			break;
		
		case SIT_PNT_NAV:			// new point is selected by clicking or navigating 
			if (new_sel_item != SelectedPoint) {
				outobj.check_pnt = true;
				newseg = getCurrSegForPnt (new_sel_item);     // get segment for point
				if (newseg != SelectedSegment)         // if a different segment will be selected
					outobj.check_seg = true;          
			}
			break;
		
		case SIT_PNT_DEL:
			if (SelectedPoint.boundtype == SEG_INTERIOR && SelectedPoint.next.boundtype == SEG_BOUNDARY)
				outobj.check_seg = true;
			break;
		
		case SIT_PNT_APP:
		case SIT_PNT_INS_AFTER:
			outobj.check_pnt = true;
			if (situation == SIT_PNT_APP || SelectedPoint.next == null) {                // if appending to end of route
				if (OpenRoute.pending_new_seg && OpenRoute.pointlist.length > 1)         // maybe redundant to check both
					outobj.check_seg = true;
			}
			break;

		case SIT_PNT_INS_BEFORE:
			outobj.check_pnt = true;
			if (SelectedPoint.boundtype == SEG_BOUNDARY && SelectedPoint.prev != null)  
				   // if inserting before segment boundary (but not first point of route)
				outobj.check_seg = true;
			break;
	}
	
	// Return output object
	return outobj;        
}


/********************************************************************
// Check fields for checktime = TIME_NEXT situation
// and display error message if appropriate.
// Input: recs_to_check - Object containing the following data members:
*    - check_seg - boolean - True if segment fields must be checked
*    - check_pnt - boolean - True if point fields must be checked
// Return: true if checks succeeded, false if not
********************************************************************/
FieldInfo.prototype.DoNextChecks = function (recs_to_check) {
	var rc = true;

	if (recs_to_check.check_seg) {
		rc = this.checkFields (SEG, TIME_NEXT);      // check selected segment
	}
	if (rc && recs_to_check.check_pnt) {       
		rc = this.checkFields (PNT, TIME_NEXT);      // check selected point
	}
	return rc;
}

/********************************************************************
// Check combinations of segment and/or point fields to see if 
// they are valid.
// Input: recs_to_check - Object containing the following data members:
*    - check_seg - boolean - True if segment fields must be checked
*    - check_pnt - boolean - True if point fields must be checked
// Return: true if checks succeeded, false if not
********************************************************************/
FieldInfo.prototype.checkCombinations = function (recs_to_check) {
	return true;
}

/********************************************************************
// Check all saved segment and point data for correctness, but
// only those fields with check time property = TIME_DONE.  
// For selected segment or point, check screen fields instead of data
// in memory model (which may not have been refreshed yet).
// Return: true if checks succeeded, false if not
*********************************************************************/
/* FieldInfo.prototype.checkSavedSegPntData = function () {
	// TO DO - whole routine
	// But not needed now because no point or segment fields have check time of TIME_DONE.
	return true;
} */ 

/*******************************************************************
// Update bndpnt field (indicating whether current point is a 
// segment boundary) for currently selected point.
// Need to do when segments inserted, etc. or when new point selected.
*******************************************************************/
FieldInfo.prototype.setSegBoundaryField = function () {
	var elmt;

	elmt = document.getElementById ("bndpnt");
	if (SelectedPoint.boundtype == SEG_BOUNDARY)   // if boundary point
		elmt.value = FLD_YES;
	else 
		elmt.value = FLD_NO;
}


/*******************************************************************
// Update lat and lng fields on the screen based on currently selected point.
*******************************************************************/
FieldInfo.prototype.setLatLngFields = function () {
	if (SelectedPoint != null) {
		// Update latitude and longitude fields on the screen
		var lat_elmt = document.getElementById ("lat");
		var lng_elmt = document.getElementById ("lng");
		lat_elmt.value = SelectedPoint.lat;
		lng_elmt.value = SelectedPoint.lng; 
	}
}



/*******************************************************************
// Map the rectype (PNT, SEG or RTE) to the actual current global 
// object of the specified type, i.e. SelectedPoint, SelectedSegment
// or OpenRoute (or SelectedRoute?).  Return a reference to the object.
*******************************************************************/
FieldInfo.prototype.getObjByType = function (rectype) {
	var obj;

	if (rectype == undefined || rectype == null) {
		obj = null;
	}
	else {
		if (rectype == PNT) 
			obj = SelectedPoint;
		else if (rectype == SEG)
			obj = SelectedSegment;
		else if (rectype == RTE)
			obj = OpenRoute;
		else
			obj = null;
	}
	
	return obj;
}




/***********************************************************************************************************/
/***********************************************************************************************************
// Custom validation routines           
// These accept, and must be passed, two parameters each: 
//   fldname - Name of form field (and name of entry in Fields object)
//   fldval - Value of the field to be checked
// Each returns a return code set to true if check passes, false otherwise.
***********************************************************************************************************/
/***********************************************************************************************************/
/*******************************************************************

/*******************************************************************
// Check speed value
*******************************************************************/
function check_speed (fldname, fldval) {
	// Not needed
	return true;
}
/***********************************************************************************************************/
/*******************************************************************


/*******************************************************************
// Gray out form field
// Three field "looks" are possible:
//  1. Input permitted - White background with default border
//  2. Read-only ("locked") but record editable - Gray background with default border
//  3. Field disabled because no record selected - 
//      	elmt.disabled = "disabled";
//			backgroundColor = #DDDDDD
//			borderColor = #BBBBBB
//			borderWidth = 1px
//			borderStyle = solid
*******************************************************************/



