var STY_TABLE = '~stytab';
var STY_CAPTION = '~stytcap';
var STY_THEAD = '~stythd';
var STY_TBODY = '~stytbod';
var STY_HEADTR = '~styhdtr';
var STY_TR = '~stytr';
var STY_TH = '~styth';
var STY_TD = '~stytd';

TAB_TABLE = 0;
TAB_DATA = 1;
TAB_HEADER = 2;
TAB_CAPTION = 3;
TAB_FORMAT = 4;

var bInitialised = false
var formTemplates = {};
var stylesheets = {};
var selectorGroups = {};


function frmInit()
{
	if (! bInitialised)
	{
		// Create the default Form stylesheet
		// NB The default sheet must precede any following sheets that
		// need to override the default.
		frmMainStylesheet = makeFrmMainStylesheet();
		bInitialised = true;
	}
}


function makeFrmMainStylesheet()
{
	// Create the Form default base stylesheet
	var rule;
	var sheet = new FrmStyleSheet('frmbase');

	rule = new FrmCSSStyleRule('.frminlblk', { display: 'inline-block', 'vertical-align': 'top' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmblock', { margin: '0', padding: '0', border: '1px solid', 
		'border-color': '#808080', overflow: 'hidden', width: 'auto', height: 'auto' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmline', { margin: '0', padding: '0', border: '1px solid', 'border-color': '#808080', 
		color: '#000000', overflow: 'hidden', width: 'auto', height: 'auto', 'min-width': '1em' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmtabs', { margin: '0', padding: '0', 
		border: 'none', color: '#678EC2', 'background-color': 'transparent', 
		overflow: 'hidden', width: '100%', height: 'auto', 'min-height': '1.2em' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmtab', { margin: '0.3em 0 0 0', padding: '0', color: '#678EC2', 
		//'border-top': '1px solid #678EC2', 'border-right': '1px solid #FFFFFF', 
		//'border-bottom': 'none', 'border-left': '1px solid #FFFFFF', 
		'border-right': '2px solid #FFFFFF', padding: '0 0.3em 0 0.3em',
		'background-color': '#DEE7F1', overflow: 'hidden', width: 'auto', 'min-width': '1em', 
		height: '1.4em', 'min-height': '1em', cursor: 'pointer' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmtabopen', { 'background-color': '#CAD8E8',
		color: '#345784', 'font-weight': 'bold' //, 'border-color': '#678EC2' 
		} );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmtabshut', { 'background-color': '#DEE7F1'
		//'border-color': '#A3BBDB', 
		//, opacity: '0.5' } );
		} );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmtab:hover', { 'background-color': '#CAD8E8' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmfield', { margin: '0', padding: '0.1em', border: '1px solid',
		'border-color': '#A0A0A0', 'background-color': '#FFFFFF', color: '#000000', overflow: 'hidden', 
		width: '6em', height: '1.5em', 'vertical-align': 'middle' } );
	sheet.insertRule(rule);

	// This rule is to fix a problem whereby "elem.style.float = 'right'" is ignored.
	// We use 'float: right' to align input elems etc in a column all with their right 
	// edges aligned one above the other.
	// But it doesn't work in IE6 or IE7, where it causes elem to align downwards.
	// So currently some fields are out of alignment. Need to find another way, or
	// some way to do the same thing in bOldIE.
	if (bOldIE)
		rule = new FrmCSSStyleRule('.frmfloatright', { float: 'none' } );
	else
		rule = new FrmCSSStyleRule('.frmfloatright', { float: 'right' } );
	sheet.insertRule(rule);

	if (bIE)
	{
		rule = new FrmCSSStyleRule('.frmbutton, .frmtoggle', { margin: '0', padding: '0.2em', 
			width: 'auto', height: '1.1em', border: '2px solid', 
			'border-color': '#FFFFFF #606060 #606060 #FFFFFF', 
			'background-color': '#D0D0D0', color: '#000000', overflow: 'hidden', 
			'text-align': 'center', cursor: 'pointer' } );
		sheet.insertRule(rule);
	}
	else
	{
		rule = new FrmCSSStyleRule('.frmbutton, .frmtoggle', { margin: '0', padding: '0.2em', 
			width: 'auto', height: '1.1em', border: '2px outset', 
			'border-color': '#FFFFFF #606060 #606060 #FFFFFF', 
			'background-color': '#D0D0D0', color: '#000000', overflow: 'hidden', 
			'text-align': 'center', cursor: 'pointer' } );
		sheet.insertRule(rule);
		rule = new FrmCSSStyleRule('.frmbutton:active', { 'border-style': 'inset' } );
	}
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmcheckbox', { height: '18px', width: '18px', margin: '0', padding: '0', 
		'border-color': '#4878B6', 'background-color': '#EFEFFF', color: '#355987', border: '2px solid', 
		'font-weight': 'bold' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmradio', { height: '17px', width: '15px', margin: '0', padding: '0', 
		'background-color': '#EFEFFF', color: '#355987', border: '5px solid', 
		'border-top-color': '#728BAC', 'border-right-color': '#C3CDDB', 'border-bottom-color': '#C3CDDB', 
		'border-left-color': '#728BAC', 'font-weight': 'bold' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmtext', { margin: '0', padding: '0', 'text-align': 'center',
		color: '#000000', overflow: 'hidden', height: 'auto' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmselect', { margin: '0', padding: '0' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmform', { 'font-size': '16px', 'font-family': 'Arial', 
		'font-style': 'normal', 'font-weight': 'normal', 'text-decoration': 'none', 
		margin: '1em', padding: '0.2em', width: 'auto', 'min-width': '40em', 
		height: 'auto', 'min-height': '30em', overflow: 'auto', 'background-color': '#FFFFFF', 
		border: '3px ridge', 'border-color': '#808080', color: '#000000' } );
	sheet.insertRule(rule);
	//rule = new FrmCSSStyleRule('.frmdisabled', { opacity: '0.4' } );
	//sheet.insertRule(rule);


	// Cannot use border-collapse: separate; because IE doesn't support border-spacing, 
	// so cells have spaces between borders

	//rule = new FrmCSSStyleRule('.frmtable', { overflow: 'auto', 'font-size': '100%', margin: '0 1em 1em 1em', padding: '0.5em', 
	//	'border-right': '2pt outset #A3BBDB', 'border-bottom': '2pt outset #A3BBDB', 'border-left': '2pt outset #A3BBDB', 
	//	color: '#2B486D', 'background-color': '#F1F5F9', 'border-collapse': 'collapse',
	//	'border-spacing': '2px', 'caption-side': 'top', 'empty-cells': 'show', 'table-layout': 'auto' } );
	//sheet.insertRule(rule);
	//rule = new FrmCSSStyleRule('tbody.frmtable', { overflow: 'auto' } );
	//sheet.insertRule(rule);
	//rule = new FrmCSSStyleRule('col.frmtable', { 'border-style': 'none' } );
	//sheet.insertRule(rule);
	//rule = new FrmCSSStyleRule('th.frmtable', { border: '2px ridge #A3BBDB', 'font-weight': 'bold', 
	//	color: '#2B486D', padding: '0.2em' } );
	//sheet.insertRule(rule);
	//rule = new FrmCSSStyleRule('td.frmtable', { border: '2px ridge #A3BBDB', color: '#000000', 
	//	padding: '0.2em', width: '8em', height: '1.2em' } );
	//sheet.insertRule(rule);
	//rule = new FrmCSSStyleRule('td.frmtable input', { border: '0', 'background-color': 'transparent', 
	//	color: '#000000', width: '6em', 'vertical-align': 'middle' } );
	//sheet.insertRule(rule);

	rule = new FrmCSSStyleRule('.frmtable table', { 'table-layout': 'auto', width: 'auto', height: 'auto', 
		overflow: 'hidden', 'border-collapse': 'collapse', 'border-spacing': '2px', 
		'text-indent': '0', color: '#385780', 'background-color': 'inherit' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmtable caption', { height: '1.5em', 'text-align': 
		'center', 'font-size': '120%', padding: '2px', border: '1px solid #96A8C0' } );
	//rule = new FrmCSSStyleRule('colgroup', {  } );
	sheet.insertRule(rule);
	//rule = new FrmCSSStyleRule('col1', { width: '18em' } );
	//sheet.insertRule(rule);
	//rule = new FrmCSSStyleRule('tfoot', {  } );
	//sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmtable thead', { height: 'auto', overflow: 'visible', 
		'vertical-align': 'middle', 'border-bottom': '3px solid #96A8C0' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmtable tbody', { height: 'auto', overflow: 'visible', 'vertical-align': 'middle' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmtable tr', { 'vertical-align': 'inherit', height: 'auto' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.frmtable th', { width: 'auto', height: 'auto', 
		'white-space': 'normal', 'font-weight': 'bold', padding: '0.2em', 
		'vertical-align': 'inherit', color: '#8091A8', 	border: '1px solid #96A8C0'} );
	sheet.insertRule(rule);

	if (bIE)
	{
		// IE doesn't implement the width/max-width style by truncating (overflow: hidden).
		// It wraps the text remorselessly.
		rule = new FrmCSSStyleRule('.frmtable td', { height: 'auto',
			'white-space': 'nowrap', overflow: 'hidden', border: '1px solid #96A8C0', 
			padding: '0.2em', 'text-align': 'inherit', 'vertical-align': 'inherit' } );
		sheet.insertRule(rule);
	}
	else
	{
		rule = new FrmCSSStyleRule('.frmtable td', { width: '20em', 'max-width': '20em', height: 'auto',
			'white-space': 'nowrap', overflow: 'hidden', border: '1px solid #96A8C0', 
			padding: '0.2em', 'text-align': 'inherit', 'vertical-align': 'inherit' } );
		sheet.insertRule(rule);
	}

	sheet.addStyleSheetToDoc();
	return sheet;
}


// Add an inherits() function to the Function object to facilitate sub-prototyping
Function.prototype.inherits = function(baseProto)
{ 
	this.prototype = new baseProto;
	this.prototype.constructor = this;
	//this.prototype.basePrototype = baseProto.prototype;
	//this.prototype.baseConstructor = baseProto;
	return this;
} 


function textwidth(text, fontsize)
{
	var elem = document.getElementById("textwidth");
	elem.style.fontSize = fontsize;
	elem.innerHTML = text;
	var txtwidth = elem.clientWidth;
}


function domPropSyntax(sProp)
{
	// Force CSS syntax props to have DOM syntax. E.g: change text-align to textAlign
	var i;
	while ((i = sProp.indexOf('-')) != -1)
		sProp = sProp.substring(0, i) + sProp.charAt(i + 1).toUpperCase() + sProp.substring(i + 2);
	return (sProp);
}


function cssPropSyntax(sProp)
{
	// Force DOM syntax props to have CSS syntax. E.g: change textAlign to text-align
	var sCssProp = '';
	for (var i = 0; i < sProp.length; i ++)
	{
		if (isUpper(sProp.charAt(i)))
			sCssProp += '-' + sProp.charAt(i).toLowerCase();
		else
			sCssProp += sProp.charAt(i);
	}
	return (sCssProp);
}


function getElementForm(elem)
{
	var form;

	while (elem && ! elem.srcForm && elem.nodeName != 'BODY')
		elem = elem.parentNode;
	if (elem && elem.srcForm)
		form = elem.srcForm;
	return form;
}


function Form(Args)
{
	// Id, if not set in Args, will be set, in add(), to '<parent-id>' + '.' + <seq>
	this.id;
	// True if this is a Root Form
	this.rootForm;
	// Initial value to be assigned to element content
	this.value;
	// 'datasource' is initialised with its data, either by the caller Args
	// or by Form.bindData(), and bound to the DOM element by Form.bindData().
	this.datasource;
	// 'elem' is the HTML element in the DOM that will display in the webpage
	this.elem;
	// 'parent' is the parent Form of this Form, or null if this is the Root Form
	this.parent;
	// childForms is null until a child Form is added to this form
	this.childForms;
	// Set true by subclass constructor if Form is a Line, or Line-type class, where all 
	//child Forms must be layed out horizontally (set to 'inline-block').
	this.isLine;
	// Stylesheet to be added to the document by fcCreateStyleSheet
	this.stylesheet;
	// Save the style derived from Args until parent known when form is added
	this.style;
	// Save the classname derived from Args until parent known when form is added
	this.classname;
	// If Form is disabled (frm.setEnabled(false)), elem is grayed and input disabled
	this.enabled = true;
	// Total box width, including margin, of widest child Form in Block, or all Children 
	// in Line, in em units.
	this.childWidthEm = 0;
	// Set width to accommodate this.childWidthEm
	this.fitwidth = false;
	// Fontsize, margin size - needed to calc childWidthEm. Set by setStyle() 
	// or from parent in add().
	this.fontsizepx = 0, this.marglefpx = 0, this.margrigpx = 0;
	// If form has a label, save it for Form.add()
	this.label;
	this.labelSide;
	this.labelStyle;
	this.labelFormWidth;
	this.labelFormHeight;
	//this.elemLabel;
	// The 'state' of the form, e.g. true or false with toggle Buttons.
	// A general purpose property that could be a string, object, etc.
	this.state = false;
	// True if form state is toggleable, e.g. toggle button.
	this.toggle = false;
	// The form that coordinates certain events for a group of forms.
	this.groupForm;
	// The form that controls button groups.
	this.buttonGroupForm;
	// If true, user may deselect the currently selected button in a buttonGroup
	// so that no buttons are selected. Normal behaviour is to deselect a button
	// by selecting another button in the group.
	this.allowDeselect = false;
	// The event-handling functions for groupForm events, e.g. button clicks.
	this.groupFuncs;
	// When this form is the groupForm for a group of forms that has a 'card' layout,
	// this property points to the currently open form in the group.
	this.openForm;
	// Border styles
	this.bordTopSty;
	this.bordRigSty;
	this.bordBotSty;
	this.bordLefSty;
	// Predominant style
	this.bordSty;
	// Background colour
	this.backCol;
	// If inheritFont is false, the form will not inherit the parent's font if
	// the parent's font is changed. See Form.setFont() for explanation.
	//this.inheritFont = false;
	this.openDisplayStyle;
	this.formIndex;
	this.dataset;

	// 'template' is set by the Form subclass constructor to the FormTemplate 
	// used to create a new Form object.
	if (! this.template)
	{
		this.template = {};

//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
		this.template.constructorFunc = 'Form()';
		this.template.constructorArgs = {};
		if (Args)
		{
			for (var prop in Args)
			{
				if	(	prop != 'datasource' && 
						prop != 'value' &&
						prop != 'fitwidth' &&
						//prop != 'isLine' &&
						prop != 'label' &&
						prop != 'labelSide' &&
						prop != 'labelStyle' &&
						prop != 'labelFormWidth' &&
						prop != 'labelFormHeight' &&
						prop != 'eventFuncs' &&
						prop != 'groupForm' &&
						prop != 'groupFuncs' &&
						prop != 'buttonGroupForm' &&
						prop != 'allowDeselect' &&
						prop != 'sTip' &&
						prop != 'rootForm' &&
						prop != 'style' &&
						prop != 'stylesheet'
					)
					this.template.constructorArgs [prop] = Args [prop];
			}
		}
	}

	if (! set(Args) || ! set (Args.sTag))
		return;

	if (set(Args.id))
		this.id = Args.id;

	if (set(Args.datasource))
		this.datasource = new Datasource(Args.datasource);

	try
	{
		this.elem = document.createElement(Args.sTag);
		// Reverse pointer from the DOM elem to this Form object
		this.elem.srcForm = this;
		if (this.id)
			this.elem.id = this.id;
	}
	catch(e)
	{
		return null;
	}

	if (set(Args.value && typeof(Args.value) == 'string'))
	{
		this.value = Args.value;
		// If value is 'datasource' the initial value will be set from
		// form.datasource.data in form.add().
		if (this.value.toLowerCase() != 'datasource')
			this.elem.innerHTML = this.value;
	}
	// DOM METHOD: (MUCH SLOWER than direct assignment to innerHTML)
	// this.elem.appendChild(document.createTextNode(this.value));

//	else if (set(Args.value && typeof(Args.value) == 'object'))
//		this.elem.value = Args.value;

	// Set any attributes
	if (set(Args.atts))
	{
		for (att in Args.atts)
			this.elem.setAttribute(att, Args.atts [att]);
	}

	//if (set(Args.rootForm))
	//	this.rootForm = Args.rootForm;

	//if (set(Args.isLine))
	//	this.isLine = Args.isLine;
	if (! set(this.isLine))
		this.isLine = false;

	if (set(Args.fitwidth))
		this.fitwidth = Args.fitwidth;

	if (set(Args.toggle))
		this.toggle = Args.toggle;

	if (set(Args.allowDeselect))
		this.allowDeselect = Args.allowDeselect;

	if (set(Args.sTip))
	{
		this.elem.title = Args.sTip;
		// DOM METHOD: (MUCH SLOWER than direct assignment to elem.title)
		// this.elem.setAttribute("title",Args.sTip);
	}

	//if (set(Args.inheritFont))
	//	this.inheritFont = Args.inheritFont;

	if (set(Args.label))
	{
		this.label = Args.label;
		if (set(Args.labelSide))
			this.labelSide = Args.labelSide;
		if (set(Args.labelStyle))
			this.labelStyle = Args.labelStyle;
		if (set(Args.labelFormWidth))
			this.labelFormWidth = Args.labelFormWidth;
		if (set(Args.labelFormHeight))
			this.labelFormHeight = Args.labelFormHeight;
	}

	if (set(Args.stylesheet))
	{
		if (Args.stylesheet.cssRules)
		{
			var id = Args.stylesheet.id;

			if (! id)
				id = this.id;
// RFSS
			this.stylesheet = fcCreateStyleSheet(id, Args.stylesheet.cssRules);
		}

//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
		// Form.stylesheet is handled separately in createFormTemplate() because it may
		// be modified during design in Form Creator.
if (this.template.constructorArgs)
			delete this.template.constructorArgs.stylesheet;
	}

	if (Args.style && (typeof(Args.style) == 'object'))
	{
		this.style = Args.style;

//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
		// Form.style is handled separately in createFormTemplate() because it may
		// be modified during design in Form Creator.
if (this.template.constructorArgs)
		delete this.template.constructorArgs.style;
	}

	if (Args.classname)
	{
		this.classname = Args.classname;
		if (Args.formClass)
			this.classname = Args.formClass + ' ' + this.classname;
	}
	else
	{
		if (Args.formClass)
			this.classname = Args.formClass;
	}

	if (set(Args.eventFuncs))
		this.eventFuncs = Args.eventFuncs;

	//if (set(this.eventFuncs) && this.eventFuncs instanceof Array)
	//{
	//	// this.eventFuncs must be a two-dimensional array of EventHandlers,
	//	// each containg an array of EventType,HandlerFunc pairs. E.g.:
	//	// [['click', clickFunc, ],['mouseover', mouseoverFunc]]
	//	for (var i = 0; i < this.eventFuncs.length; i ++)
	//	{
	//		if	(	this.eventFuncs [i] instanceof Array && 
	//				this.eventFuncs [i].length == 2 &&
	//				typeof(this.eventFuncs [i][0]) == 'string' &&
	//				this.eventFuncs [i][1] instanceof Function
	//			)
	//			setEventListener(this.elem, this.eventFuncs [i][0], this.eventFuncs [i][1]);
	//		
	//	}
	//}
	if (set(this.eventFuncs))
		this.setEventFuncs(this.eventFuncs);


	if (set(Args.groupForm))
		this.groupForm = Args.groupForm;

	if (set(Args.groupFuncs))
		this.groupFuncs = Args.groupFuncs;

	// If this form has a groupForm, set an event listener on this form for each event type 
	// in the groupForm's groupFuncs array of event listeners.
	//if (this.groupForm && this.groupForm.groupFuncs && this.groupForm.groupFuncs instanceof Array)
	//{
	//	// groupFuncs must have the same structure as eventFuncs above
	//	var groupFuncs = this.groupForm.groupFuncs;
	//	for (var i = 0; i < groupFuncs.length; i ++)
	//	{
	//		if	(	groupFuncs [i] instanceof Array && 
	//				groupFuncs [i].length == 2 &&
	//				typeof(groupFuncs [i][0]) == 'string' &&
	//				groupFuncs [i][1] instanceof Function
	//			)
	//			setEventListener(this.elem, groupFuncs [i][0], groupFuncs [i][1]);
	//	}
	//}
	if (this.groupForm && this.groupForm.groupFuncs)
		this.setEventFuncs(this.groupForm.groupFuncs);


	if (set(Args.buttonGroupForm))
		this.buttonGroupForm = Args.buttonGroupForm;

	//if (this.buttonGroupForm && this.buttonGroupForm.groupFuncs && this.buttonGroupForm.groupFuncs instanceof Array)
	//{
	//	// groupFuncs must have the same structure as eventFuncs above
	//	var groupFuncs = this.buttonGroupForm.groupFuncs;
	//	for (var i = 0; i < groupFuncs.length; i ++)
	//	{
	//		if	(	groupFuncs [i] instanceof Array && 
	//				groupFuncs [i].length == 2 &&
	//				typeof(groupFuncs [i][0]) == 'string' &&
	//				groupFuncs [i][1] instanceof Function
	//			)
	//			setEventListener(this.elem, groupFuncs [i][0], groupFuncs [i][1]);
	//	}
	//}
	if (this.buttonGroupForm && this.buttonGroupForm.groupFuncs)
		this.setEventFuncs(this.buttonGroupForm.groupFuncs);

	this.toFuncName = function() { return 'Form'; };
	this.toString = this.toFuncName;
}


Form.prototype.setEventFuncs = function(aEventFuncs)
{
	// aEventFuncs must be a two-dimensional array of EventHandlers,
	// each containg an array of EventType,HandlerFunc pairs. E.g.:
	// [['click', clickFunc, ],['mouseover', mouseoverFunc]]
	if (aEventFuncs instanceof Array)
	{
		this.eventFuncs = aEventFuncs;

		for (var i = 0; i < aEventFuncs.length; i ++)
		{
			if	(	aEventFuncs [i] instanceof Array && 
					aEventFuncs [i].length == 2 &&
					typeof(aEventFuncs [i][0]) == 'string' &&
					aEventFuncs [i][1] instanceof Function
				)
				setEventListener(this.elem, aEventFuncs [i][0], aEventFuncs [i][1]);
		}
	}
}


Form.prototype.setStyle = function(elem, style, classname)
{
	// Set the elem.style and/or elem.className properties from 'style' and/or 'classname' args.
	//
	// The 'style' arg is an object whose properties are css declarations. The property name is
	// the selector, e.g. 'border', and the property value is the declaration, e.g. 'solid 1px'.
	// The property value is assigned to the corresponding elem.style property.
	//
	// The 'classname' arg is a string of one or more class names, e.g 'panwarn' or 'cu2 k4 b1',
	// and the string is appended to elem.className.
	//
	// E.g: setStyle(myElem, { border: 'solid 1px', color: 'red'}, 'panwarn hidden' );
	//
	// If any classname is prefixed by a minus '-', it is removed. E.g if classname = 
	// 'cl1 -cl2 cl3', then 'cl2' is removed from elem.className and 'cl1 and 'cl3' are added. 
	// If a single '-' is encountered in the classname, elem.className is cleared at that point. 
	// E.g if elem.className was 'cla clb cl2', and style = '- cl1 cl3', then elem.className will 
	// be set to 'cl1 cl3'.
	//
	// To remove any other property value assign an empty string to the property name,
	//
	// E.g setStyle(myElem, { border: '' } );

	// Process elem.className before elem.style as that is the order that the browser will do it

	if (classname && typeof(classname) == 'string')
	{
		// Set elem.className
		var aClass = classname.split(' ');

		for (var i = 0; i < aClass.length; i ++)
		{
			var clsname = aClass [i];

			if (clsname == '-')
				elem.className = '';
			else
			{
				var bRemove = false;
				if (clsname.charAt(0) == '-')
				{
					clsname = clsname.substring(1);
					bRemove = true;
				}
				sRe = '(^|\\s+)' + clsname + '(\\s+|$)';
				var re = new RegExp(sRe);

				if (bRemove && elem.className)
				{
					while (elem.className.search(re) != -1)
						elem.className = elem.className.replace(re, ' ');
				}
				else
				{
					if (! elem.className)
						elem.className = clsname;
					else if (elem.className.search(re) == -1)
					{
						if (elem.className.length)
							clsname = ' ' + clsname;
						elem.className += clsname;
					}
				}
			}
		}

// TODO Are these necessary?????
		// Set some required Form properties
		var sPropName, sPropVal, saPropNames;

		sPropVal = fcGetStylePropertyFromStore('font-size', dotPrefix(elem.className), null, false);
		if (sPropVal)
			this.getPixSize('font-size', sPropVal);
		var oDeclarations = fcGetStylePropertyFromStore('margin', dotPrefix(elem.className), null, true);
		if (oDeclarations)
		{
			var sPropName;

			sPropVal = oDeclarations [sPropName = 'margin-left'];
			if (sPropVal)
				this.getPixSize(sPropName, sPropVal);
			sPropVal = oDeclarations [sPropName = 'margin-right'];
			if (sPropVal)
				this.getPixSize(sPropName, sPropVal);
		}
		// Get the border style and background colour for button states
		oDeclarations = fcGetStylePropertyFromStore('border', dotPrefix(elem.className), null, true);
		if (oDeclarations)
		{
			this.bordTopSty = oDeclarations ['border-top-style'];
			this.bordRigSty = oDeclarations ['border-right-style'];
			this.bordBotSty = oDeclarations ['border-bottom-style'];
			this.bordLefSty = oDeclarations ['border-left-style'];
			// If all four are the same, set the predominant style
			if	(	(this.bordTopSty == this.bordRigSty) && 
					(this.bordTopSty == this.bordBotSty) && 
					(this.bordTopSty == this.bordLefSty)
				)
				this.bordSty = this.bordTopSty;
		}
		this.backCol = fcGetStylePropertyFromStore('background-color', dotPrefix(elem.className), null, false);

		// Get all style properties in elem.className into elem.style. This is required for
		// style modification by the Form Creator's Style Tools.
		// 
		// TODO >>> If we have this, do we need the individual ones, such as this.bordTopSty stored
		// in the form object any more? We can get them out of elem.style.xxxx.
		//oDeclarations = fcGetStylePropertyFromStore(null, dotPrefix(elem.className), null, true);
		//if (oDeclarations)
		//{
		//	var prop;

		//	for (var prop in oDeclarations)
		//		elem.style [domPropSyntax(prop)] = oDeclarations [prop];
		//}
	}

	if (style && typeof(style) == 'object')
	{
		// Set elem.style
		for (var prop in style)
		{
			var sDomProp = domPropSyntax(prop);
			var sCssProp = cssPropSyntax(prop);

			elem.style [sDomProp] = style [prop];


// TODO>>> Everything below here to the end of the block - is it necessary anymore?


			if (sCssProp == 'font-size')
				this.getPixSize(sDomProp, elem.style [sDomProp]);
			if (sCssProp.substring(0,6) == 'margin')
			{
				if (sCssProp == 'margin')
				{
					var oDeclarationBlock = parseCssDeclaration(sCssProp, elem.style [sDomProp]);
					for (var prop in oDeclarationBlock)
						this.getPixSize(prop, oDeclarationBlock [prop]);
				}
				else
					this.getPixSize(sCssProp, elem.style [sDomProp]);
			}
			if (sDomProp.substring(0,6) == 'border')
			{
				var oDeclarations = parseCssDeclaration(sCssProp, elem.style [sDomProp]);
				if (oDeclarations)
				{
					var bordTopSty = oDeclarations ['border-top-style'];
					if (bordTopSty)
						this.bordTopSty = bordTopSty;
					var bordRigSty = oDeclarations ['border-right-style'];
					if (bordRigSty)
						this.bordRigSty = bordRigSty;
					var bordBotSty = oDeclarations ['border-bottom-style'];
					if (bordBotSty)
						this.bordBotSty = bordBotSty;
					var bordLefSty = oDeclarations ['border-left-style'];
					if (bordLefSty)
						this.bordLefSty = bordLefSty;
					// If all four are the same, set the predominant style
					if	(	(this.bordTopSty == this.bordRigSty) && 
							(this.bordTopSty == this.bordBotSty) && 
							(this.bordTopSty == this.bordLefSty)
						)
						this.bordSty = this.bordTopSty;
					else
					{
						if (this.bordTopSty || this.bordRigSty || this.bordBotSty || this.bordLefSty)
							// If any border's style has been set in elem.style, set bordSty to
							// null in case it was set from classname.
							this.bordSty;
					}
				}
			}
			if (sCssProp.substring(0,10) == 'background')
			{
				var oDeclarations = parseCssDeclaration(sCssProp, elem.style [sDomProp]);
				if (oDeclarations)
				{
					var backCol = oDeclarations ['background-color'];
					if (backCol)
						this.backCol = backCol;
				}
			}
		}
	}
}


Form.prototype.addStyleRule = function(sProperty, sValue)
{
	var sDomProp = domPropSyntax(sProperty);
	this.elem.style [sDomProp] = sValue;
	// Add the rule into form.style for the Form Template
	if (! this.style)
		this.style = {};
	this.style [sDomProp] = sValue;
}


Form.prototype.childFormCount = function()
{
	var iChildForms = 0;

	if (this.childForms)
	{
		for (var prop in this.childForms)
			++ iChildForms;
	}
	return iChildForms;
}


function dotPrefix(className)
{
	// Prefix all classnames in a className list with '.', to comply with
	// CSS selector syntax used in FrmCSSStyleRule objects.
	var saClassNames, sClassNames = '';

	if (className)
	{
		saClassNames = className.split(' ');
		for (var i = 0; i < saClassNames.length; i ++)
		{
			var name = '.' + saClassNames [i];
			if (sClassNames.length)
				name = ' ' + name;
			sClassNames += name;
		}
	}
	return sClassNames;
}


Form.prototype.getPixSize = function(sCssProperty, sSize)
{
	// Get the size of various element properties in pixels.
	var iUnit;

	if (! sCssProperty || ! sSize)
		return;

	if (sSize == 'auto')
	{
		if (sCssProperty == 'margin-right' || sCssProperty == 'marginRight')
			this.margrigpx = 0;
		else if (sCssProperty == 'margin-left' || sCssProperty == 'marginLeft')
			this.marglefpx = 0;
	}
	else if ((iUnit = sSize.indexOf('px')) != -1)
	{
		var iPx;

		iPx = Number(sSize.substring(0, iUnit));
		if (sCssProperty == 'font-size' || sCssProperty == 'fontSize')
			this.fontsizepx = iPx;
		else if (sCssProperty == 'margin-right' || sCssProperty == 'marginRight')
			this.margrigpx = iPx;
		else if (sCssProperty == 'margin-left' || sCssProperty == 'marginLeft')
			this.marglefpx = iPx;
	}
	else if ((iUnit = sSize.indexOf('%')) != -1 && this.parent)
	{
		var iPc, iBaseSizePx;

		iPc = Number(sSize.substring(0, iUnit));
		if (sCssProperty == 'font-size' || sCssProperty == 'fontSize')
			this.fontsizepx = (iPc / 100) * this.parent.fontsizepx;
		else if (sCssProperty.substring(0, 6) == 'margin')
		{
			if (this.fontsizepx)
				iBaseSizePx = this.fontsizepx;
			else
				iBaseSizePx = this.parent.fontsizepx;
			if (sCssProperty == 'margin-right' || sCssProperty == 'marginRight')
				this.margrigpx = (iPc / 100) * iBaseSizePx;
			else if (sCssProperty == 'margin-left' || sCssProperty == 'marginLeft')
				this.marglefpx = (iPc / 100) * iBaseSizePx;
		}
	}
	else if ((iUnit = sSize.indexOf('em')) != -1)
	{
		var iEm, iBaseSizePx, iParentFontsizePx;

		if (this.parent)
			iParentFontsizePx = this.parent.fontsizepx;
		else
			iParentFontsizePx = 16;
		iEm = Number(sSize.substring(0, iUnit));
		if (sCssProperty == 'font-size' || sCssProperty == 'fontSize')
			this.fontsizepx = iEm * iParentFontsizePx;
		else if (sCssProperty.substring(0, 6) == 'margin')
		{
			if (this.fontsizepx)
				iBaseSizePx = this.fontsizepx;
			else
				iBaseSizePx = iParentFontsizePx;
			if (sCssProperty == 'margin-right' || sCssProperty == 'marginRight')
				this.margrigpx = iEm * iBaseSizePx;
			else if (sCssProperty == 'margin-left' || sCssProperty == 'marginLeft')
				this.marglefpx = iEm * iBaseSizePx;
		}
	}
}


Form.prototype.add = function(form, beforeForm)
{
	// The root form must be displayed before any other form in a formset is added.
	//
	// Form.add() will not work correctly, and cannot be called, until the root form
	// has been added to the DOM, and no child or ancestor of the root form has 
	// 'display: none'. The style 'visibility: hidden' is allowed, and may be used if 
	// the Form must not appear on the screen. If the initial state of any form must 
	// be 'display: none', for example to create a 'card' layout, it must be set when
	// all forms have been added.
	//
	// The reason for this is that the property 'elem.offsetWidth' is used in some layout
	// calculations, and this property is zero until the browser actually renders the DOM.
	//
//>>> This simply won't work because even then it relies on no form having child forms
// added to it until it, itself, has been added, otherwise its child forms will not have
// their offsetWidth set, so its offsetWidth will be wrong.
// E.g:	This is OK:		A.add(B); B.add(C);
//		This is NOT OK:	B.add(C); A.add(B); 

	// First check that 'this' parent form has been processed by this function.
	// E.G. if 'this' is the root form, it may have been added to the DOM by
	// element.appendChild() rather than Form.add().
//	if (! this.added)
//	{
//		this.added = true; // Avoid an endless loop
//		this.add(this);
//	}


//NB TODO>>> We are doing away with the concept of a root form. A form with a nested collection
//of child forms, that may have been regarded as a root form when created, must be able to be 
// added to some other form which, itself, may be a child form, etc.... 
//
// Also, in line with the idea that a form can be plugged into any other form as a child, we 
// would like to do away with the form.parent property (*NO, see end of para*). The style, and 
// any other attributes which are inherited from, or depend on, the parent form, can do so 
// dynamically in form.add(). NOT a good idea to drop form.parent. It is too valuable in
// ascending up ancestor forms, even though this can be done by using the link 
// 'form.elem.parentNode.srcForm'.
//
// The part played in all this by the form.id property is critical. A form must obviously have a
// unique id among its sibling forms, but it is probably essential that the id be unique in the
// document as a whole, not just in case we use document.getElementbyId(), but also in the case
// of datasource bindings, etc.


	// The next 3 lines were the original rootForm logic, based on a Form being 
	// actively assigned the rootForm property at the time of creation. It may be
	// more realistic, and more useful, to assume that any form that does not have a
	// parent Form at the time of addition of a child Form is, by definition, a 
	// rootForm. Conversely any Form that is a rootForm at the time of addition into
	// a parent Form is, by definition, no longer a rootForm.
// TODONOW>>> Surely the form.rootForm property is redundant because it is exactly the converse
// of this.parent. If this.parent is not set then form.rootForm is true, and vice-versa.
// Try replacing it everywhere with that test. Also, for all the above reason, it is not
// needed in the Form template.

	// Root Form has no parent
	//if (! form.rootForm)
	//	form.parent = this;
	if (! this.parent)
	{
		// This is the rootForm
		if (this.rootForm != this)
		{
			// This is the first time it has been added to. Init the rootForm.
			this.rootForm = this;
			this.formIndex = {};
			this.formIndex [this.id] = this;

			// Assign the default rootForm id, if one has not been assigned through the 
			// Form constructor, and set the default font size to the CSS standard 16px.
			if (! this.id)
				this.id = '0'; // Assign the Root Form default id
			this.elem.id = this.id;
			this.setStyle(this.elem, this.style, this.classname);
			if (! this.fontsizepx)
				// Shouldn't be possible - frmroot has the rootform fontsizepx.
				// But 16 is the universal browser standard 'normal' font.
				this.fontsizepx = 16;
		}

		// If the form being added was a rootForm, its formIndex must be merged into this
		// actual rootForm form.formIndex before being reset to the this.formIndex.
		if (set(form.rootForm) && form.formIndex !== this.formIndex)
		{
			var prop;
			for (prop in form.formIndex)
				this.formIndex [prop] = form.formIndex [prop];
		}
	}

	form.rootForm = this.rootForm;
	form.parent = this;
	form.formIndex = this.formIndex;

	if (! set(form.id))
	{
		// Ensure a unique id
		var iSeq = 0, existingElem = this.elem;
		var sUniqueId;
		while (existingElem)
		{
			existingElem = document.getElementById(sUniqueId = this.id + '.' + (iSeq ++));
		}
		form.id = sUniqueId;
	}
	form.elem.id = form.id;

	this.formIndex [form.id] = form;

	// DOM METHOD: (MUCH SLOWER than direct assignment to elem.id)
	// form.elem.setAttribute('id', form.id);

	// Add form to parent's childForms
	if (! this.childForms)
		this.childForms = {};
	this.childForms [form.id] = form;

	// Now that the form's parent is known, set the form elem.style and elem.classname
	if (form.style || form.classname)
		form.setStyle(form.elem, form.style, form.classname);

	// Set the form's font-* styles according to its inheritFont property
	//form.setFont();

// RFSS
	// If the form has a stylesheet, merge it with the rootForm's stylesheet
	// or add it directly into the document.
	// NO - the form.stylesheet should be added on its own because otherwise
	// the rootForm template stylesheet will include the merged stylesheets.
	// This might result in a different appearance when the Form hierarchy is
	// recreated from the template, and also it duplicates the stylesheet in
	// the root and child forms, and in their templates, and it detracts from
	// the concept of each Form being totally self-contained, stylewise.
	//if (form.stylesheet)
	//{
	//	// Merge form.stylesheet into rootForm.stylesheet
	//	if (! form.rootForm && blkFcRootForm && blkFcRootForm.stylesheet)
	//		blkFcRootForm.stylesheet.mergeStylesheet(form.stylesheet);
	//	else
	//		// 'form' is rootForm or rootForm has no stylesheet or not using 
	//		// Form Creator - default to window.document.
	//		form.stylesheet.addStyleSheetToDoc();
	//}
	// If the form has a stylesheet, add it into the document.
	if (form.stylesheet)
	{
		if (! form.stylesheet.id)
		{
			form.stylesheet.id = form.id;
			if (form.stylesheet.styleElem)
				form.stylesheet.styleElem.id = form.id;
		}
		form.stylesheet.addStyleSheetToDoc();
	}

	var elemToAdd;

	if (form.label)
	{
		// Create a wrapper div and add the label and form into it. Return the wrapper.
		elemToAdd = form.addLabel();
	}
	else
		elemToAdd = form.elem;

	// If this form (the parent) is a Line-type, force the child form to have the style 
	// 'inline-block'. If it is not a Line, leave the block/inline display property as is.
	if (this.isLine)
	{
		if (bOldIE)
		{
			elemToAdd.style.display = 'inline';
			elemToAdd.style.zoom = '1.0';
		}
		else
		{
			elemToAdd.style.display = 'inline-block';
//			elemToAdd.style.verticalAlign = 'middle';
		}
		if (elemToAdd.className)
			elemToAdd.className += ' frminlblk';
		else
			elemToAdd.className = 'frminlblk';
	}

	if (this.tabs)
	{	
		// Add a tab for this new child form
		var tab = new Tab();
		//notneeded tab.tabid = 'tab_' + form.id;
		tab.tabform = form;
		form.tab = tab;
		if (form.tabval)
			tab.value = form.tabval;
		else
			tab.value = form.id.charAt(0).toUpperCase() + form.id.substring(1);
		tab.elem.innerHTML = tab.value;
		tab.setEventFuncs([['click', openTabClick ]]);
		this.tabs.add(tab);
		// If parent form contains more than one child, leave the current
		// open child as the open tab and shut this child.
		if (this.childFormCount() > 1)
		{
			form.shut();
			form.tab.shut();
		}
	}

	if (beforeForm && this.childForms [beforeForm.id])
		this.elem.insertBefore(elemToAdd, beforeForm.elem);
	else
		this.elem.appendChild(elemToAdd);

	if (! form.fontsizepx)
		form.fontsizepx = this.fontsizepx;

	// If 'fitwidth' is true, adjust the width to widest child (Block), or sum of 
	// child widths (Line).
	// If current width has been adjusted in Form creator, only do this if current width 
	// is too small to accomodate child or children.
	// Apply to all ancestor Forms as well. 
	// See '*UpdateStyle* Changing a Form's dimensions' in Form Creator code.
	// Everything below here needs *UpdateStyle*.
	var parentForm = this, childForm = form;

	while (parentForm != null && parentForm.fitwidth)
	{
// For some reason the next line doesn't work properly because (parentForm.isLine) 
// evaluates to true even if parentForm.isLine is false.
// During debugging Firebug sometimes showed 'isLine' as the object 'parentForm',
// as if the assignment was 'isLine = parentForm' instead of isLine = 'parentForm.isLine'.

//		if (parentForm.isLine)
		var isLine = parentForm.isLine;
		if (isLine)
		{
			// Set parentForm.childWidthEm to the total widths of all childForms

			// CSS 8.3.1: "In CSS 2.1, horizontal margins never collapse"
			var childWidthPx = 0;
			// Extra space on right for safety and for next child insertion
			var iLineExtraPx = 4;

			for (var prop in parentForm.childForms)
			{
				var elem;
				childForm = parentForm.childForms [prop];
				if (childForm.elemWrap)
					elem = childForm.elemWrap;
				else
					elem = childForm.elem;
				childWidthPx += childForm.marglefpx + elem.offsetWidth + 
					childForm.margrigpx + iLineExtraPx;
			}
			parentForm.childWidthEm = childWidthPx / childForm.fontsizepx;
		}
		else
		{
			// If the width of childForm is wider than parentForm's current widest 
			// child (parentForm.childWidthEm), update parentForm.childWidthEm. 
			var elem;
			if (childForm.elemWrap)
				elem = childForm.elemWrap;
			else
				elem = childForm.elem;
			var iTotWidthIncMargEm = (childForm.marglefpx + elem.offsetWidth +
				childForm.margrigpx) / childForm.fontsizepx;
			if (iTotWidthIncMargEm > parentForm.childWidthEm)
				parentForm.childWidthEm = iTotWidthIncMargEm;
		}

		if (typeof(parentForm.childWidthEm) == 'number')
		{
			var sWidth = parentForm.childWidthEm + 'em';
			// parentForm.setStyle(parentForm.elem, { width: sWidth, height: 'auto' }); why change height?
			parentForm.setStyle(parentForm.elem, { width: sWidth });
		}

		childForm = parentForm;
		parentForm = childForm.parent;
	}

	// Because 'vertical-align' seems to be ignored in so many cases, emulate 
	// it by setting the child's top margin to half the spare vertical space 
	// in the parent content area.
	var parentElem;
	if (form.label)
		parentElem = elemToAdd;
	else
		parentElem = this.elem;

	// This will only work if the child is the only child in the parent, where the parent 
	// is a block box, or if the child is within a Line-type and has 'inline-block'.
	if	(	form.elem.style.verticalAlign.length && 
			(parentElem.childNodes.length == 1 || form.elem.style.display == 'inline-block')
		)
	{
		var iSpace = parentElem.clientHeight - form.elem.offsetHeight;
		if (iSpace > 0)
		{
			if (form.elem.style.verticalAlign == 'top')
			{
				form.elem.style.marginTop = '0';
				form.elem.style.marginBottom = iSpace + 'px';
			}
			else if (form.elem.style.verticalAlign == 'middle')
			{
				form.elem.style.marginTop = (iSpace / 2) + 'px';
				form.elem.style.marginBottom = '0';
			}
			else if (form.elem.style.verticalAlign == 'bottom')
			{
				form.elem.style.marginTop = iSpace + 'px';
				form.elem.style.marginBottom = '0';
			}
			form.elem.style.verticalAlign == '';
		}
	}

	form.openDisplayStyle = getDeclarededStyleCss(form.elem, 'display');
	if (form.openDisplayStyle == 'none')
		form.openDisplayStyle = 'block';

	// If this.value is set to 'datasource' and datasource was set in the Args, bind it immediately.
	if	(	this.value && this.value.toLowerCase() == 'datasource' && 
			this.datasource && this.datasource.data
		)
		this.bindData(this.datasource);
}


Form.prototype.deleteForm = function(form)
{
	var formToDelete;

	if (form)
		formToDelete = form;
	else
		formToDelete = this;

	if (formToDelete.childForms)
		formToDelete.deleteChildForms();

	// Delete the DOM Form.elem
	if (formToDelete.elem && formToDelete.elem.parentNode)
		formToDelete.elem.parentNode.removeChild(formToDelete.elem);
	if (formToDelete.elem)
		delete formToDelete.elem;
	// Delete the Form
	if	(	formToDelete.parent && 
			formToDelete.parent.childForms && 
			formToDelete.parent.childForms[formToDelete.id]
		)
		delete formToDelete.parent.childForms[formToDelete.id];
	// If no parent, this is a rootForm. Delete it directly.
	if (formToDelete)
		delete formToDelete;
}


Form.prototype.deleteChildForms = function()
{
	if (this.childForms)
	{
		for (var prop in this.childForms)
			this.childForms [prop].deleteForm();
	}
}


Form.prototype.replace = function(oldForm, newForm)
{
	// Replace the child form oldForm with newForm. If oldForm
	// doesn't exist, just add newForm.
	var bDeleted = false, beforeForm = null;
	if (this.childForms)
	{
		for (var prop in this.childForms)
		{
			if (prop == oldForm.id)
			{
				this.deleteForm(oldForm);
				bDeleted = true;
			}
			else
			{
				if (bDeleted)
				{
					beforeForm = this.childForms [prop];
					break;
				}
			}
		}
	}
	this.add(newForm, beforeForm);
}


Form.prototype.setEnabled = function(enabled)
{
	this.enabled = enabled;
	if (enabled)
	{
		this.elem.style.opacity = '1';
		this.elem.style.filter = 'alpha(opacity=100)';
	}
	else
	{
		this.elem.style.opacity = '0.4';
		this.elem.style.filter = 'alpha(opacity=40)';
	}
	if (this.elemLabel)
	{
		if (enabled)
		{
			this.elemLabel.style.opacity = '1';
			this.elemLabel.style.filter = 'alpha(opacity=100)';
		}
		else
		{
			this.elemLabel.style.opacity = '0.4';
			this.elemLabel.style.filter = 'alpha(opacity=40)';
		}
	}
	if (typeof(this.elem.disabled) != 'undefined')
		this.elem.disabled = ! enabled;
}


Form.prototype.setVisible = function(visible)
{
	if (this.elem)
	{
		if (visible)
			this.elem.style.display = 'visible';
		else
			this.elem.style.display = 'hidden';
	}
}


Form.prototype.open = function()
{
	if (this.openDisplayStyle)
		this.elem.style.display = this.openDisplayStyle;
	else
		this.elem.style.display = 'block';
}


Form.prototype.shut = function()
{
	if (this.elem)
		this.elem.style.display = 'none';
}


Form.prototype.addTabs = function(parentForm)
{
	if (this.constructorFunc != 'Block()')
		return null;
	
	var tabs = new Tabs();

	if (! parentForm)
		parentForm = this;

	// Insert the tabs child form above any existing non-tabs child forms
	var firstChild = null;
	if (this.childForms)
	{
		for (var prop in this.childForms)
		{
			// A form may have multiple Tabs forms. Look for the first non-Tabs child.
			if (! this.childForms [prop].isTabs)
			{
				firstChild = this.childForms [prop];
				break;
			}
		}
	}

	this.add(tabs, firstChild);
	parentForm.tabs = tabs;

	// Add tabs for any existing child forms
	for (var prop in parentForm.childForms)
	{
		if (! parentForm.childForms [prop].isTabs)
		{
			var tab = new Tab();
			//notneeded tab.tabid = 'tab_' + this.childForms [prop].id;
			tab.tabform = parentForm.childForms [prop];
			parentForm.childForms [prop].tab = tab;
			if (parentForm.childForms [prop].tabval)
				tab.value = parentForm.childForms [prop].tabval;
			else
				tab.value = parentForm.childForms [prop].id.charAt(0).toUpperCase() + 
					parentForm.childForms [prop].id.substring(1);
			tab.elem.innerHTML = tab.value;
			tab.setEventFuncs([['click', openTabClick ]]);
			tabs.add(tab);
		}
	}

	return tabs;
}


function openTab(form)
{
	if (! form)
		return;
	if (form.tabform)
		// form is a Tab
		form = form.tabform;
	if (! form.parent || ! form.parent.childForms)
		return;
	for (var prop in form.parent.childForms)
	{
		var childForm = form.parent.childForms [prop];
		// Ignore actual Tabs child forms
		if (childForm.isTabs)
			continue;
		if (childForm === form)
		{
			form.open();
			if (form.tab)
				form.tab.open();
		}
		else
		{
			childForm.shut();
			if (childForm.tab)
				childForm.tab.shut();
		}
	}
}


function openTabClick(ev)
{
	ev = getEventProps(ev);
	ev.stopPropagation();
	openTab(ev.target.srcForm);
}


Form.prototype.addLabel = function()
{
	// Form.label is non-null and is assumed to contain the text or image of the label. 
	// Create a div, form.elemLabel and set the label text or image into elemLabel.
	// Create a wrapper div and place the form and label elems into it in the position
	// contained in Form.labelSide: top, right, bottom or left. Set various style
	// properties to achieve the correct positioning, width, alignment, etc.

	this.elemWrap = document.createElement('div');
	this.elemLabel = document.createElement('div');
//	this.elemLabel.className = 'frmlabel';
	this.elemLabel.innerHTML = this.label;
	if (! this.labelSide)
		this.labelSide = 'left';
	if (this.labelSide == 'right' || this.labelSide == 'bottom')
	{
		this.elemWrap.appendChild(this.elem);
		this.elemWrap.appendChild(this.elemLabel);
	}
	else
	{
		this.elemWrap.appendChild(this.elemLabel);
		this.elemWrap.appendChild(this.elem);
	}

	if (this.labelStyle)
		this.setStyle(this.elemLabel, this.labelStyle);

	if (this.labelSide == 'right' || this.labelSide == 'left')
	{

		if (bOldIE)
		{
			this.elemLabel.style.display = 'inline';
			this.elem.style.zoom = '1.0';
			this.elemLabel.style.display = 'inline';
			this.elem.style.zoom = '1.0';
		}
		else
		{
			this.elemLabel.style.display = 'inline-block';
			this.elem.style.display = 'inline-block';
		}
		this.elemLabel.style.verticalAlign = 'middle';
		this.elem.style.verticalAlign = 'middle';
		if (this.labelSide == 'right')
			this.elemLabel.style.marginLeft = '0.3em';
		else
			this.elemLabel.style.marginRight = '0.3em';
	}

	if (this.labelSide == 'top' || this.labelSide == 'bottom')
		this.elemLabel.style.textAlign = 'center';

	// Move margins, width and height from this elem to wrap elem
	this.elemWrap.style.margin = this.elem.style.margin;
	this.elemWrap.style.marginTop = this.elem.style.marginTop;
	this.elemWrap.style.marginRight = this.elem.style.marginRight;
	this.elemWrap.style.marginBottom = this.elem.style.marginBottom;
	this.elemWrap.style.marginLeft = this.elem.style.marginLeft;
	this.elemWrap.style.width = this.elem.style.width;
	this.elemWrap.style.height = this.elem.style.height;
	this.elem.style.margin = '';
	this.elem.style.marginTop = '';
	this.elem.style.marginRight = '';
	this.elem.style.marginBottom = '';
	this.elem.style.marginLeft = '';
	if (this.labelFormWidth)
		this.elem.style.width = this.labelFormWidth;
	else
		this.elem.style.width = '';
	if (this.labelFormHeight)
		this.elem.style.height = this.labelFormHeight;
	else
		this.elem.style.height = '';

	this.elemWrap.style.overflow = 'hidden';

	return this.elemWrap;
}


Form.prototype.setFont = function()
{
	// TODO: Remove this function if definitely not required.
	// Originally it was permissible to insert text into Blocks, Lines, etc,
	// instead of just Texts. This meant it was possible for parent Forms to have
	// innerHtml text, and also to have child Forms. So if the parent Form's font was 
	// changed in any way, so was the child's. This function was to enable that to be 
	// prevented. But because of CSS text-decoration propagation to all child elements, 
	// regardless of the child's current text-decoration (CSS spec 16.3.1). Now, text 
	// can only exist in Texts so, although Texts can potentially have child Forms, 
	// inheritance of the font style is not a problem.

	// Called from Form.add() after Form.setStyle() has been called.
	//
	// If the form's font-* values are not set when the form is added to its parent, then, 
	// if inheritFont is true, they are left unset which has a default of 'inherit', whereas 
	// if inheritFont is false, they are set specifically to the parent's current values.
	//
	// This means that if a parent form's font is changed and the child form's inheritFont 
	// is false, the child form's fonts will remain the same, otherwise they will change 
	// with the parent.
	if (this.inheritFont)
		return;
	var saProperty = ['font-family','font-size','font-style','font-weight','text-decoration'];
	for (var i = 0; i < saProperty.length; i ++)
	{
		var styleValue = getDeclarededStyleCss(this.elem, saProperty [i]);
		if (! styleValue.length)
		{	
			// The form's font style is '' which means its has the default of 'inherit'.
			// See if the parent elem has a font style
			var parentStyleValue = getDeclarededStyleCss(this.parent.elem, saProperty [i]);
			if (parentStyleValue.length)
			{
				// Set the font style value to the parent's value
				this.elem.style [domPropSyntax(saProperty [i])] = parentStyleValue;
			}
		}
	}
}


Form.prototype.formatData = function(data, format, txtalign)
{
	// A format string may contain numeric or string or date format characters.
	// Literal strings may be mixed in and are enclosed in single quotes.
	//
	// Format characters do not use the % prefix:
	// Zero, digits and dot signify width, decimal places and zero-padding as per C printf().
	// Date characters are as per C strftime().
	// The < and > characters force left or right justification; default is
	// to justify strings on the left and numbers on the right.
	//
	// E.G "7.2" "'$ '5.2-" "d'/'m'/'y" ">30"
	var value = '';
	if (! set(data))
		return value;
	if (! format)
		format = this.format;
	if (! this.format)
		return data;

	var type = typeof(data);
	if (type == 'string' || type == 'number')
	{
// TODO Temp assume numeric format only
		var decs = 0, dot = format.indexOf('.'); //, width = 0;

		// Currently don't need width
		//var a = format.match(/\d+/);
		//if (a != null)
		//	 width = a[0];		

		if (dot != -1 && dot < format.length - 1)
			decs = format.charAt(dot + 1);
		value = Number(data).toFixed(decs);
		if (isNaN(value))
			return data;
		else
		{
			if (txtalign)
				txtalign.style = ' style="text-align:right"';
		}
	}
	else if (type == 'boolean')
	{
		return data;
	}
	else
	{
		return data;
	}

	return value;
}


//Form.prototype.setDatasource = function(data, datatype)//, id)
//{
//	// Form Creator, or other caller, must call setDatasource() on every Form except those
//	// forms that can use an ancestor form's datasource. This may mean that only the rootForm
//	// needs a datasource, or that one or more of the rootForm's child forms need their own.
//	//
//	// The default for each Form is to set datasource.data to the datasource.data of the 
//	// nearest ancestor Form with a non-null datasource.data.
//
//	if (! data)
//		return;
//
//	this.datasource = new Datasource();
//	this.datasource.data = data;
//	this.datasource.datatype = datatype;
//}


//>>>bindData does not save the passed datasource. So if the form is updated, the data
//cannot be updated back to the server.
//Also, if a DS is in the rootForm and holds the values for various fields in the form as an array,
//we need to find a way of binding the individual fields to the array values, using the colid.
//The other way would be if the DS was not an array but an object of name/value pairs.
//Also handle the situation where two fields in form use/display same data item, eg in a header
//and also in select or Text...


Form.prototype.bindData = function(datasource)
{
	// Default bindData() function, called when the Form subclass does not have a bindData().
	//
	// Its main use is to be a collection of data values, or datasources, for its child 
	// forms, although it can also bind the data to itself.
	//
	// If arg 'datasource' is passed, it overrides, but does not replace, this.datasource
	if (! datasource && this.datasource)
		datasource = this.datasource;

	if (! datasource || ! datasource.data)
		return null;

	if (! (datasource instanceof Datasource))
		datasource = new Datasource(datasource);


	if (! datasource.datatype)
		datasource.datatype = datasourceType(datasource.data);

	if	(	datasource.datatype == DS_DTATYP_DTASRC || 
			datasource.datatype == DS_DTATYP_OBJECT || 
			datasource.id != this.id
		)
	{
		// DS_DTATYP_DTASRC is a datasource.data object whose property values are all 
		// datasources, and whose property names are the ids of descendant childForms.
		// 
		// E.G. 
		// datasource.id = 'myform';
		// datasource.data ['drill5'] matches form.childForms ['drill5']
		// datasource.data ['phonenum'] matches form.childForms [3].childForms ['phonenum']
		// datasource.data ['myform'] matches this form (form.id: 'myform')
		// .....
		// datasource.datatype = DS_DTATYP_DTASRC;
		//
		// N.B.
		// Since datasource.datatype is DS_DTATYP_DTASRC, it is implied that all
		// datasource.data properties are Datasources. So, for example:
		// 	datasource.data ['drill5'].id = 'drill5' and 
		// 	datasource.data ['drill5'].data = 'No records found' and
		// 	datasource.data ['drill5'].datatype = DS_DTATYP_STRING
		var datasources;

		if (datasource.datatype == DS_DTATYP_DTASRC || datasource.datatype == DS_DTATYP_OBJECT)
			datasources = datasource.data;
		else
			// Datasource has an id which doesn't match this form's id. Search for a 
			// descendant form to bind it to.
			datasources = [ datasource.data ];

		for (var dsprop in datasources)
		{
			var pForms = [ this ];
			var cFormProps = [ null ];
			var bFound = false;

			// First check this form
			if (this.id == dsprop)
			{
				this.bindData(datasources [dsprop]);
				continue;
			}
			
			// Check all child forms
			while (pForms.length && ! bFound)
			{
				var bLoopEnd = true;
				var cForms = pForms [pForms.length - 1].childForms;
				var curFrmprop = cFormProps [cFormProps.length - 1];

				if (cForms)
				{
					for (var frmprop in cForms)
					{
						if (curFrmprop)
						{
							if (frmprop == curFrmprop)
								curFrmprop = null;
						} 
						else if (frmprop == dsprop)
						{
							cForms [frmprop].bindData(datasources [dsprop]);
							bFound = true;
							break;
						}
						else if (cForms [frmprop].childForms)
						{
							pForms.push(cForms [frmprop]);
							cFormProps [cFormProps.length - 1] = frmprop;
							cFormProps.push(null);
							bLoopEnd = false;
							break;
						}
					}
				}
				if (bLoopEnd)
				{
					pForms.pop();
					cFormProps.pop();
				}
			}
		}
	}
	else if (datasource.datatype == DS_DTATYP_VALUE)
	{
		var value = datasource.data;

		if (this.format)
			value = this.formatData(value);

		if (typeof(this.elem.value) != 'undefined')
			this.elem.value = value;
		else
		{
			try
			{
				this.elem.innerHTML = value;
			}
			catch (e)
			{
				var dataElem = document.createTextNode(value);
				this.elem.appendChild(dataElem);
			}
		}
	}
	else if (datasource.datatype == DS_DTATYP_ARRAY )
	{
		// If the 1st element of the array is the keyword '_arrayinit_',
		// arrayInitialiser() will create a new array with the specified dimensions.
		// Otherwise it will return the passed arg unchanged.
		var aData = arrayInitialiser(datasource.data, '');

		// Traverse the childForms passing each child form the next array element.
		// If the array element is a simple value, the child form will bind that value to
		// its DOM element.
		// If the array element is a sub-array, the child form will traverse its own 
		// childForms with the sub-array.
		var	i = 0;

		for (var prop in this.childForms)
		{
			if (i < aData.length)
			{
				var childSource = aData [i];
				var childType = datasourceType(childSource);
				//?? var childDatasource = { data: childSource, 'datasource.datatype': childType };
				var childDatasource = { data: childSource, datatype: childType };
				this.childForms [prop].bindData(childDatasource);
				++ i;
			}
			else
				// Array exhausted before childForms. Continue recursing
				// in case any child forms have their own datasource.
				this.childForms [prop].bindData(null);
		}
	}
}


Form.prototype.clearData = function()
{
	// Clear all data that has been derived from datasources with queries via
	// queryDatasource() from this form and all childForms.
	//
	// TODO: This function should be a combination and distillation of queryDatasource(),
	// datasourceHasQuery() and bindData().

	
}


function setQueryParams(form, datasource, bParamSet)
{
	if (bParamSet == 'undefined')
		bParamSet = false;

	if (! form || ! datasource)
		return false;

	if (datasource.datatype == DS_DTATYP_TABLE)
	{
		if (datasource.data.tcaption)
			bParamSet = setQueryParams(form, datasource.data.tcaption.datasource, bParamSet);
		if (datasource.data.thead)
			bParamSet = setQueryParams(form, datasource.data.thead.datasource, bParamSet);
		if (datasource.data.tbody)
			bParamSet = setQueryParams(form, datasource.data.tbody.datasource, bParamSet);
	}
	else if (datasource.datatype == DS_DTATYP_DTASRC)
	{
		for (var dsprop in datasource.data)
			bParamSet = setQueryParams(form, datasource.data [dsprop], bParamSet);
	}
	else
	{
		if (! datasource.qryparams || TypeOf(datasource.qryparams) != 'object')
			return bParamSet;

		for (formid in datasource.qryparams)
		{
			var valueForm = form.formIndex [formid];
			if (! valueForm)
				continue;
			var data;
			if (valueForm.elem.value)
				data = valueForm.elem.value;
			else
				data = valueForm.elem.innerHTML;
			if (data && ((typeof(data) == 'string' && data.length) || typeof(data) == 'number'))
			{
				datasource.qryparams [formid] = data;
				bParamSet = true;
			}
		}
	}
	return bParamSet;
}


function datasourceHasQuery(datasource)
{
	if (! datasource)
		return false;
	if (datasource.query)
		return true;
	if	(	datasource.datatype == DS_DTATYP_TABLE && datasource.data &&
			(	(
					datasource.data.tcaption && 
					datasource.data.tcaption.datasource &&
					datasource.data.tcaption.datasource.query
				) ||
				(
					datasource.data.thead && 
					datasource.data.thead.datasource &&
					datasource.data.thead.datasource.query
				) ||
				(
					datasource.data.tbody && 
					datasource.data.tbody.datasource &&
					datasource.data.tbody.datasource.query
				)
			)
		)
		return true;

	if (datasource.datatype == DS_DTATYP_DTASRC)
	{
		for (var dsprop in datasource.data)
		{
			var b = datasourceHasQuery(datasource);
			if (b)
				return true;
		}
	}

	return false;
}


Form.prototype.queryDatasource = function (oDatasource)
{
	// Query the server by sending a Datasource query. If oDatasource is set, 
	// use oDatasource, else construct a DS_DTATYP_DTASRC Datasource containing
	// all datasources within this form and all nested childForms.
	//
	// A DS_DTATYP_DTASRC is a datasource.data object whose property values are all 
	// datasources.
	//
	// The datasource.query and datasource.qrytype vars in all nested Datasources
	// must have been set to the appropriate query.
	//
	// This function will set the values in the qryparams vars to the corresponding
	// values found in the Forms whose ids are the names in the qryparams vars.
	//
	// The data and datatype vars will be set by the server to the objects, arrays, 
	// etc structures that receive the query data.//
	//
	// When the datasource is received from the server by the callback retQueryDatasource(), 
	// bind the data in it to the forms by calling bindData().
	
	if (! set(oDatasource))
	{
		oDatasource = new Datasource();
		oDatasource.id = this.id;
		oDatasource.data = {};
		oDatasource.datatype = DS_DTATYP_DTASRC;

		// First check for a datasource in this form
		if (datasourceHasQuery(this.datasource))
		{
			oDatasource.data [this.id] = this.datasource;
			if (! setQueryParams(this, this.datasource))
				return;
		}

		// Check for a datasource in all child forms
		var pForms = [ this ];
		var cFormProps = [ null ];

		while (pForms.length)
		{
			var bLoopEnd = true;
			var cForms = pForms [pForms.length - 1].childForms;
			var curFrmprop = cFormProps [cFormProps.length - 1];
				
			if (cForms)
			{
				for (var frmprop in cForms)
				{
					if (curFrmprop)
					{
						if (frmprop == curFrmprop)
							curFrmprop = null;
					} 
					else
					{
						if	(	cForms [frmprop].datasource &&
								datasourceHasQuery(cForms [frmprop].datasource)
							)
						{
							oDatasource.data [cForms [frmprop].id] = cForms [frmprop].datasource;
							if (! setQueryParams(cForms [frmprop], cForms [frmprop].datasource))
								return;
						}

						if (cForms [frmprop].childForms)
						{
							pForms.push(cForms [frmprop]);
							cFormProps [cFormProps.length - 1] = frmprop;
							cFormProps.push(null);
							bLoopEnd = false;
							break;
						}
					}
				}
			}
			if (bLoopEnd)
			{
				pForms.pop();
				cFormProps.pop();
			}
		}
	}

	var jDatasource = oDatasource.toJson();
	var sFunc = 'loadDatasource';
	var sRpcArgs = rpcArgs(sFunc, [ jDatasource ], true);

	this.httpRequest('/bin/rpc.cgi', sFunc, sRpcArgs, 'retQueryDatasource');
}


Form.prototype.retQueryDatasource = function (responseText, status)
{
	setStatus('');
	if (status == RPC_OK)
	{
		var objDatasource = rpcGetRetVal(responseText, true, jsonFuncNameToFunc);
		var objType = TypeOf(objDatasource);
		if (objType != 'object')
		{
			setStatus('Error loading Datasource (Expected Object, got Type: ' + objType + ')', C_ERROR);
			return;
		}
		// OK - Bind the Datasource to the Form
		this.bindData(objDatasource);
var x = 1;
	}
	else  
	{
		setStatus('Error loading Datasource (Request failed, status: ' + status +
			' response: ' + responseText + ')', C_ERROR);
		return;
	}
}


Form.prototype.httpRequest = function (resourcePath, sFunction, postData, callback)
{
	var form = this;
	var req, excep = '';
	if (window.XMLHttpRequest)
		req = new XMLHttpRequest();              
	else                                  
	{
		try { req = new ActiveXObject("Msxml2.XMLHTTP.6.0") } catch(e) { excep = e;
		try { req = new ActiveXObject("Msxml2.XMLHTTP.3.0") } catch(e) { excep = e;
		try { req = new ActiveXObject("Msxml2.XMLHTTP") } catch (e) { excep = e;
		try { req = new ActiveXObject("Microsoft.XMLHTTP") } catch (e) { excep = e; }}}}
	}
	if (! req)
	{
		setStatus('Error loading Datasource (null HttpRequest): ' +  excep, C_ERROR);
		return;
	}
	// Append random '?' query string to avoid caching
	if (iRpcSeq > 99)
		iRpcSeq = 0;
	var url = sHostDocRoot + resourcePath + '?W' + (iRpcSeq ++) + '.' + sFunction;
	req.open('POST', url, true);  
	req.onreadystatechange = function (ev)
	{  
		if (req.readyState == 4)
		{  
			// ContentLength available if needed
			var iContentLength = req.getResponseHeader("Content-Length");

			form [callback](req.responseText, req.status);
			//callback(req.responseText, req.status);
		}  
	};
	req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	req.setRequestHeader("Content-Length", postData.length);
	req.send(postData);
}


Block.inherits(Form); function Block(Args)
{
	if (! set(Args))
		Args = {};

	this.template = {};

	this.constructorFunc = 'Block()';
//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
	this.template.constructorFunc = this.constructorFunc;
//	this.template.constructorArgs = Args;

	Args.sTag = 'div';
	Args.formClass = 'frmblock';
	Form.call(this, Args);

	this.toFuncName = function() { return 'Block'; };
	this.toString = this.toFuncName;
}


Line.inherits(Form); function Line(Args)
{
	if (! set(Args))
		Args = {};

	this.template = {};

	this.constructorFunc = 'Line()';
//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
	this.template.constructorFunc = this.constructorFunc;
//	this.template.constructorArgs = Args;

	Args.sTag = 'div';
	Args.formClass = 'frmline';
	this.isLine = true;
	Form.call(this, Args);

	this.toFuncName = function() { return 'Line'; };
	this.toString = this.toFuncName;
}


Tabs.inherits(Form); function Tabs(Args)
{
	if (! set(Args))
		Args = {};

	this.template = {};

	this.constructorFunc = 'Tabs()';
//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
	this.template.constructorFunc = this.constructorFunc;
//	this.template.constructorArgs = Args;

	Args.sTag = 'div';
	Args.formClass = 'frmtabs';
	this.isLine = true;
	this.isTabs = true;
	Form.call(this, Args);

	this.toFuncName = function() { return 'Line'; };
	this.toString = this.toFuncName;
}


Tab.inherits(Form); function Tab(Args)
{
	if (! set(Args))
		Args = {};

	this.template = {};

	this.constructorFunc = 'Tab()';
//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
	this.template.constructorFunc = this.constructorFunc;
//	this.template.constructorArgs = Args;

	Args.sTag = 'div';
	Args.formClass = 'frmtab';
	Form.call(this, Args);

	this.toFuncName = function() { return 'Tab'; };
	this.toString = this.toFuncName;
}


Tab.prototype.open = function()
{
	this.setStyle(this.elem, null, 'frmtabopen -frmtabshut');
}


Tab.prototype.shut = function()
{
	this.setStyle(this.elem, null, '-frmtabopen frmtabshut');
}


Field.inherits(Form); function Field(Args)
{
	// Args: id, style, value, sLabel, bPassword, eventFuncs

	// If style is non-null it is a css declaration or class to be added 
	// to the primary style 'fld'. The declaration(s) in style override
	// the 'fld' primary declarations.
	if (! set(Args))
		Args = {};

	this.template = {};

	this.constructorFunc = 'Field()';
//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
	this.template.constructorFunc = this.constructorFunc;
//	this.template.constructorArgs = Args;

	Args.sTag = 'input';
	Args.formClass = 'frmfield';
	Form.call(this, Args);

	this.toFuncName = function() { return 'Field'; };
	this.toString = this.toFuncName;
}


Field.prototype.setEnabled = function(enabled)
{
	Form.prototype.setEnabled.call(this, enabled);
}


//Field.prototype.bindField = function(datasource)
Field.prototype.bindData = function(datasource)
{
	if	(	! datasource || ! datasource.data || 
			! datasource.datatype || datasource.datatype  != DS_DTATYP_VALUE
		)
		return null;

	if (! (datasource instanceof Datasource))
		datasource = new Datasource(datasource);

	var value = datasource.data;

	if (this.format)
		value = this.formatData(value);

	if (typeof(this.elem.value) != 'undefined')
		this.elem.value = value;
	else
	{
		try
		{
			this.elem.innerHTML = value;
		}
		catch (e)
		{
			var dataElem = document.createTextNode(value);
			this.elem.appendChild(dataElem);
		}
	}

	// If return value is true (this.datasource is object), used own this.datasource
	// If return value is false (this.datasource is null/undefined), used passed datasource arg
	return this.datasource;
}


Button.inherits(Form); function Button(Args)
{
	if (! set(Args))
		Args = {};

	this.template = {};

	this.constructorFunc = 'Button()';
//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
	this.template.constructorFunc = this.constructorFunc;
//	this.template.constructorArgs = Args;

	Args.sTag = 'div';
	if (set(Args.toggle))
		Args.formClass = 'frmtoggle';
	else
		Args.formClass = 'frmbutton';
	Form.call(this, Args);

	if (this.value && ! this.elem.style.width)
		this.elem.style.width = '' + ((this.value.length * 10) / 16) + 'em';

//	if (set(Args.value && typeof(Args.value) == 'object'))
//		this.elem.value = Args.value;

	this.toFuncName = function() { return 'Button'; };
	this.toString = this.toFuncName;
}


//Button.prototype.bindButton = function(datasource)
Button.prototype.bindData = function(datasource)
{
	if (this.datasource)
		datasource = this.datasource;
	if (! datasource || ! datasource.data)
		return;
	if (! (datasource instanceof Datasource))
		datasource = new Datasource(datasource);

	var datatype = datasource.datatype;
	if (! datatype)
		dataType = datasourceType(datasource.data);
	if (datatype != DS_DTATYP_VALUE)
		return;

	var value = datasource.data;

	if (this.format)
		value = this.formatData(value);

	if (typeof(this.elem.value) != 'undefined')
		this.elem.value = value;
	else
	{
		try
		{
			this.elem.innerHTML = value;
		}
		catch (e)
		{
		}
	}
}


function toggleButtonState(form, backColorIfSet, bNewState)
{
	var prevForm;

	// Ignore if form not a toggle
	if (! form.toggle)
		return false;

	if (form.buttonGroupForm)
	{
		// This button is part of a group
		if (form.state)
		{
			// This button is already the selected button. If the button
			// group does not allow deselection, return without doing anything.
			if (! form.buttonGroupForm.allowDeselect)
				return false;
			form.buttonGroupForm.selected;
		}
		else
		{
			// Reset the state of the current selected button
			prevForm = form.buttonGroupForm.selected;
			if (prevForm)
				prevForm.state = false;
			// Set this as the new selected button
			form.buttonGroupForm.selected = form;
		}
		form.state = ! form.state;
	}
	else
	{
		// If the arg 'bNewState' has been passed, set the button to bNewState
		// instead of toggling. This is invalid if form is part of a button group.
		if (set(bNewState))
			form.state = bNewState;
		else
			form.state = ! form.state;
	}

	// Toggle the button's appearance by setting the border style
	// to groove, ridge, inset or outset, depending on borderstyle.
	// If 'selected' has two states: yes/no, then the corresponding styles are:
	// inset/outset or groove/ridge
	var aFm = [ form ];
	if (prevForm)
		aFm.push(prevForm);
	for (var f = 0; f < aFm.length; f ++)
	{
		var fm = aFm [f];

		if (fm.bordSty)
		{
			if (fm.bordSty == 'inset' || fm.bordSty == 'outset')
			{
				if (fm.state)
					fm.elem.style.borderStyle = 'inset';
				else
					fm.elem.style.borderStyle = 'outset';
			}
			else if (fm.bordSty == 'groove' || fm.bordSty == 'ridge')
			{
				if (fm.state)
					fm.elem.style.borderStyle = 'groove';
				else
					fm.elem.style.borderStyle = 'ridge';
			}
		}
		// Set the background
		if (fm.backCol)
		{
			// To set rgb lighter = rgb + ((256 - rgb) * fraction)
			// To set rgb darker = rgb - (rgb * fraction)
			// But we assume that Form.backCol is set to the normal (unselected) state
			if (fm.state)
			{
				if (backColorIfSet)
				{
					fm.elem.style.backgroundColor = backColorIfSet;
				}
				else
				{
					var r, g, b;
					
					// >>> NB TODO: This assumes colour is in #xxxxxx form !!!
					r = parseInt(fm.backCol.substring(1,3), 16);
					g = parseInt(fm.backCol.substring(3,5), 16);
					b = parseInt(fm.backCol.substring(5,7), 16);
					r = r - Math.floor(r * 0.15);
					g = g - Math.floor(g * 0.15);
					b = b - Math.floor(b * 0.15);

					r = r.toString(16);
					if (r.length == 1)
						r = '0' + r;
					g = g.toString(16);
					if (g.length == 1)
						g = '0' + g;
					b = b.toString(16);
					if (b.length == 1)
						b = '0' + b;
					fm.elem.style.backgroundColor = '#' + r + g + b;
				}
			}
			else
				fm.elem.style.backgroundColor = fm.backCol;
		}
	}
	return true;
}


Text.inherits(Form); function Text(Args)
{
	if (! set(Args))
		Args = {};

	this.template = {};

	this.constructorFunc = 'Text()';
//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
	this.template.constructorFunc = this.constructorFunc;
//	this.template.constructorArgs = Args;

	Args.sTag = 'div';
	Args.formClass = 'frmtext';
	Form.call(this, Args);
	if (! set(this.value))
	{
		this.value = '&nbsp;';
		this.elem.innerHTML = this.value;
	}
//	if (set(Args.value && typeof(Args.value) == 'object'))
//		this.elem.value = Args.value;

	this.toFuncName = function() { return 'Text'; };
	this.toString = this.toFuncName;
}


//Text.prototype.bindText = function(datasource)
Text.prototype.bindData = function(datasource)
{
	if (this.datasource)
		datasource = this.datasource;
	if (! datasource || ! datasource.data)
		return;
	if (! (datasource instanceof Datasource))
		datasource = new Datasource(datasource);
	var datatype = datasource.datatype;
	if (! datatype)
		dataType = datasourceType(datasource.data);
	if (datatype != DS_DTATYP_VALUE)
		return;

	var value = datasource.data;

	if (this.format)
		value = this.formatData(value);

	if (typeof(this.elem.value) != 'undefined')
		this.elem.value = value;
	else
	{
		try
		{
			this.elem.innerHTML = value;
		}
		catch (e)
		{
			var dataElem = document.createTextNode(value);
			this.elem.appendChild(dataElem);
		}
	}
}

//Replace Checkbox with Button
//Checkbox.inherits(Form); function Checkbox(Args)
//{
//	if (! set(Args))
//		Args = {};
//
//	this.template = {};
//	this.template.constructorFunc = this;
//	this.template.constructorArgs = Args;
//
//	Args.sTag = 'input';
//	Arg.atts = { type: 'checkbox' };
//	Args.formClass = 'frmcheckbox';
//	Form.call(this, Args);
//
////	this.bindData = bindCheckbox;
//
//	this.toFuncName = function() { return 'Checkbox'; };
//	this.toString = this.toFuncName;
//}
//
//
//function bindCheckbox()
//{
//	if (typeof(this.datasource.data) == 'boolean')
//		this.elem.checked = this.datasource.data;
//}
//
//
////Replace Radio with Button
//Radio.inherits(Form); function Radio(Args)
//{
//	if (! set(Args))
//		Args = {};
//
//	this.template = {};
//	this.template.constructorFunc = this;
//	this.template.constructorArgs = Args;
//
//	Args.sTag = 'input';
//	Args.atts = { type: 'radio' };
//	Args.formClass = 'frmradiobutton';
//	Form.call(this, Args);
//
////	this.bindData = bindRadio;
//
//	this.toFuncName = function() { return 'Radio'; };
//	this.toString = this.toFuncName;
//}
//
//
//function bindRadio()
//{
//	if (typeof(this.datasource.data) == 'boolean')
//		this.elem.checked = this.datasource.data;
//}


Select.inherits(Form); function Select(Args)
{
	// TODO Select 'type' arg type must allow choice, combo, list
	if (! set(Args))
		Args = {};

	this.template = {};

	this.constructorFunc = 'Select()';
//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
	this.template.constructorFunc = this.constructorFunc;
//	this.template.constructorArgs = Args;

	Args.sTag = 'select';
	Args.formClass = 'frmselect';
	Form.call(this, Args);

	if (Args.options && (Args.options instanceof Array))
	{
		for (var i = 0; i < Args.options.length; i ++)
		{
			var option = new Option(Args.options [i]);
			this.elem.options [i] = option;
		}
	}

	this.toFuncName = function() { return 'Select'; };
	this.toString = this.toFuncName;
}


//>>> make toString() in the base function independent of the literal
// 'Block' or 'Form' or 'Field' by substituting some property that yields the 
// required string. Better still, put the toString() func inside inherits().
Select.prototype.toString = function()
{
	return 'Select';
}


Select.prototype.bindData = function(datasource)
{
	if (this.datasource)
		datasource = this.datasource;
	if (! datasource || ! datasource.data)
		return;
	var datatype = datasource.datatype;
	if (! datatype)
		dataType = datasourceType(datasource.data);
	if (datatype != DS_DTATYP_ARRAY)
		return;

	this.elem.options.length = 0;
	for (var i = 0; i < this.datasource.data.length; i ++)
	{
		var option = new Option(this.datasource.data [i]);
		this.elem.options [i] = option;
	}
}

// ************************ Table *******************************************

// Using W3C DOM table methods:
//-------------------------------

//Table.inherits(Form); function Table(Args)
//{
//	if (! set(Args))
//		Args = {};
//	this.template = {};
//
//	this.constructorFunc = 'Table()';
////ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
//	this.template.constructorFunc = this.constructorFunc;
//	this.template.constructorArgs = {};
////TODO>>> NB template.caption.datasource and template.constructorArgs.caption.datasource must never exist.
//// Check that this is the case after a call to new Table() and createTemplate...
//	Args.sTag = 'table';
//	Args.formClass = 'frmtable';
//	Form.call(this, Args);
//
//	// The value in caption, header and body may be set in Args.caption/header/body 
//	// or left blank and loaded by bindData.
//
//	// CAPTION
//	if (Args.caption && typeof(Args.caption) == 'object')
//	{
//		this.caption = Args.caption;
//		this.caption.elem = this.elem.createCaption();
//		if (this.caption.style)
//			this.setStyle(this.caption.elem, this.caption.style);
//	}
//
//	// HEADER
//	if (Args.thead && typeof(Args.thead) == 'object')
//	{
//		this.thead = Args.thead;
//		this.thead.elem = this.elem.createTHead();
//		if (this.thead.style)
//			this.setStyle(this.thead.elem, this.thead.style);
//	}
//
//	// BODY
//	if (Args.tbody && typeof(Args.tbody) == 'object')
//		this.tbody = Args.tbody;
//	else
//		this.tbody = {};
//
//
////ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
//	if (Args)
//	{
//		if (Args.caption)
//			Args.caption = {};
//		if (Args.thead)
//			Args.thead = {};
//		if (Args.tbody)
//			Args.tbody = {};
//		for (var prop in Args)
//		{
//			if	(	prop != 'datasource' && 
//					prop != 'style' &&
//					prop != 'stylesheet'
//				)
//				this.template.constructorArgs [prop] = Args [prop];
//		}
//	}
//
//	this.tbody.elem = document.createElement('tbody');
//	this.elem.appendChild(this.tbody.elem);
//
//	if (this.tbody.style)
//		this.setStyle(this.tbody.elem, this.tbody.style);
//
//
//	// If caption, thead or tbody have been created in Args, bindData() will bind
//	// them now, otherwise they must be bound later by a separate call to bindData().
//	if (this.datasource)
//		//this.bindTable(this.datasource);
//		this.bindData(this.datasource);
//
//	this.toFuncName = function() { return 'Table'; };
//	this.toString = this.toFuncName;
//}

// Using direct creation of table HTML:
//-------------------------------------
// Currently (2009) over twice as fast as Table methods in all browsers
// except Windows IE6, IE7 where it is over 60 times faster.
//---------------------------------------------------------------------


Table.inherits(Form); function Table(Args)
{
	if (! set(Args))
		Args = {};
	this.template = {};

	this.constructorFunc = 'Table()';
//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
	this.template.constructorFunc = this.constructorFunc;
	this.template.constructorArgs = {};
//TODO>>> NB template.caption.datasource and template.constructorArgs.caption.datasource must never exist.
// Check that this is the case after a call to new Table() and createTemplate...
	Args.sTag = 'div';
	//Args.formClass = 'frmtable';
	Form.call(this, Args);

	var aTableHtml = new Array();
	aTableHtml.push('<table>');

	// The value in caption, header and body may be set in Args.caption/header/body 
	// or left blank and loaded by bindData.

	// CAPTION
	//if (Args.caption && typeof(Args.caption) == 'object')
	//{
	//	aTableHtml.push('<caption></caption>');
	//}
	aTableHtml.push('<caption></caption>');

	// HEADER
	//if (Args.thead && typeof(Args.thead) == 'object')
	//{
	//	aTableHtml.push('<thead></thead>');
	//}
	aTableHtml.push('<thead></thead>');

	// BODY
	aTableHtml.push('<tbody></tbody>');

	aTableHtml.push('</table>');
	this.elem.innerHTML = aTableHtml.join('');
	
	this.table = this.elem.getElementsByTagName('table')[0];

	if (this.table)
		this.setStyle(this.table, null, 'frmtable');
	if (Args.caption && Args.caption.style && this.table.caption)
		this.setStyle(this.table.caption, Args.caption.style);
	if (Args.thead && Args.thead.style && this.table.thead)
		this.setStyle(this.table.thead, Args.thead.style);
	if (Args.tbody && Args.tbody.style && this.table.tbody)
		this.setStyle(this.table.tbody, Args.tbody.style);


	// If caption, thead or tbody have been created in Args, bindData() will bind
	// them now, otherwise they must be bound later by a separate call to bindData().
	if (this.datasource)
		//this.bindTable(this.datasource);
		this.bindData(this.datasource);

	this.toFuncName = function() { return 'Table'; };
	this.toString = this.toFuncName;
}


//function fillDataThead()
//{
//	var headerRow = this.thead.elem.insertRow(-1);
//	for (var i = 0; i < this.thead.value.length; i ++)
//	{
//		var headerCell = headerRow.insertCell(-1);
//		headerCell.innerHTML = this.thead.value [i];
//	}
//}


// This doesn't seem to be necessary since the datatype DS_DTATYP_TABLE contains 
// a complete table datasource structure, which is passed to Form.setDatasource()
// and assigned to this.datasource.
//Table.prototype.setTableDatasource = function()
//{
//	// Called from Form.setDatasource(), which has created this.datasource.data
//	// and this.datasource.datatype.
//
//	if (this.datasource.datatype != DS_DTATYP_TABLE)
//		return;
//
//	if (this.caption && this.datasource.data.caption)
//		this.caption.datasource = this.datasource.data.caption;
//	if (this.thead && this.datasource.data.thead)
//		this.thead.datasource = this.datasource.data.thead;
//	if (this.tbody && this.datasource.data.tbody)
//		this.tbody.datasource = this.datasource.data.tbody;
//}


//Table.prototype.bindData = function(datasource)
//{
//	if (datasource.datatype != DS_DTATYP_TABLE)
//		return;
//	if (! (datasource instanceof Datasource))
//		datasource = new Datasource(datasource);
//
//	var colid = datasource.data.colid;
//	if (colid)
//	{
//		// The colid is an array of column ids matching corresponding columns in
//		// the thead and tbody rows. IDs are always lowercase.
//		for (var col = 0; col < colid.length; col ++)
//			colid [col] = String(colid [col]).toLowerCase();
//	}
//
//	if (this.caption && datasource.data.caption)
//	{
//		var capDtaSrc = datasource.data.caption.datasource;
//		var datatype = capDtaSrc.datatype;
//		if (! datatype)
//			datatype = datasourceType(capDtaSrc.data);
//		//this.caption.datasource = capDtaSrc;
//		if (datatype == DS_DTATYP_VALUE)
//			this.caption.elem.innerHTML = capDtaSrc.data;
//		else if (datatype == DS_DTATYP_ARRAY)
//		{
//			// Array stuff ...
//		}
//		// .... etc ...
//	}
//
//	if (this.thead && datasource.data.thead)
//	{
//		var theadDtaSrc = datasource.data.thead.datasource;
//		var datatype = theadDtaSrc.datatype;
//		if (! datatype)
//			datatype = datasourceType(theadDtaSrc.data);
//		//this.thead.datasource = theadDtaSrc;
//		// The data must be a 2-dimensional array because Table header may contain multiple rows
//		if	((datatype == DS_DTATYP_ARRAY) && theadDtaSrc.data.length)
//		{
//			var aaThead;
//			// Check for an array initialiser
//			var theadSource = arrayInitialiser(theadDtaSrc.data);
//
//			// Remove any existing rows
//			while (this.thead.elem.rows.length)
//				this.thead.elem.deleteRow(-1);
//
//			if (! (theadSource [0] instanceof Array))
//				// Force aaThead to be 2-dim row array with 1 child row array
//				aaThead = [ theadSource ];
//			else
//				aaThead = theadSource;
//
//			for (var row = 0; row < aaThead.length; row ++)
//			{
//				var tr = this.thead.elem.insertRow(-1);
//				for (var col = 0; col < aaThead [row].length; col ++)
//				{
//					var cell = document.createElement('th');
//					tr.appendChild(cell);
//					if (aaThead [row][col])
//						cell.innerHTML = aaThead [row][col];
//					else
//						cell.innerHTML = '&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp';
//				}
//			}
//		}
//		// else
//		// .... object, etc ...
//	}
//
//	if (this.tbody && datasource.data.tbody)
//	{
//		var tbodyDtaSrc = datasource.data.tbody.datasource;
//		var datatype = tbodyDtaSrc.datatype;
//		if (! datatype)
//			datatype = datasourceType(tbodyDtaSrc.data);
//		//this.tbody.datasource = tbodyDtaSrc;
//		// The data must be a 2-dimensional array of row arrays
//		if	((datatype == DS_DTATYP_ARRAY) && tbodyDtaSrc.data.length)
//		{
//			var aaTbody;
//			// Check for an array initialiser
//			var tbodyData = arrayInitialiser(tbodyDtaSrc.data);
//
//			// Remove any existing rows
//			while (this.tbody.elem.rows.length)
//				this.tbody.elem.deleteRow(-1);
//
//			if (! (tbodyData [0] instanceof Array))
//				// Force aaTbody to be 2-dim row array with 1 child row array
//				aaTbody = [ tbodyData ];
//			else
//				aaTbody = tbodyData;
//
//			//for (var row = 0; row < aaTbody.length; row ++)
//			//{
//			//	var tr = this.tbody.elem.insertRow(-1);
//			//	for (col = 0; col < aaTbody [row].length; col ++)
//			//	{
//			//		var cell = tr.insertCell(-1);
//			//		if (aaTbody [row][col])
//			//		{
//			//			// Uncomment below if formatting here using datasource.data.colfmt 
//			//			//var val = Number(aaTbody [row][col]).toFixed(2);
//			//			//if (isNaN(val))
//			//				cell.innerHTML = aaTbody [row][col];
//			//			//else
//			//			//	cell.innerHTML = val;
//			//		}
//			//		else
//			//			cell.innerHTML = '&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp';
//			//	}
//			//}
//			var aTbody = new Array();
//			for (var row = 0; row < aaTbody.length; row ++)
//			{
//				aTbody.push('<tr>');
//				for (col = 0; col < aaTbody [row].length; col ++)
//				{
//					var val;
//					if (aaTbody [row][col])
//					{
//						// Uncomment next 2 lines if formatting here using datasource.data.colfmt 
//						//var val = Number(aaTbody [row][col]).toFixed(2);
//						//if (isNaN(val))
//							val = aaTbody [row][col];
//					}
//					else
//						val = '&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp';
//					aTbody.push('<td>' + val + '</td>');
//				}
//				aTbody.push('</tr>');
//			}
//			this.tbody.elem.innerHTML = 'abc';
//			var s = aTbody.join('');
//			this.tbody.elem.innerHTML = s;
//			//this.tbody.elem.innerHTML = aTbody.join('');
//var x = 1;
//		}
//		// else
//		// .... object, etc ...
//	}
//	// If return value is true (this.datasource is object), used own this.datasource
//	// If return value is false (this.datasource is null/undefined), used passed datasource arg
//	return this.datasource;
//}


Table.prototype.bindData = function(datasource)
{
	if (datasource.datatype != DS_DTATYP_TABLE)
		return;
	if (! (datasource instanceof Datasource))
		datasource = new Datasource(datasource);

	var colid = datasource.data.colid;
	if (colid)
	{
		// The colid is an array of column ids matching corresponding columns in
		// the thead and tbody rows. IDs are always lowercase.
		for (var col = 0; col < colid.length; col ++)
			colid [col] = String(colid [col]).toLowerCase();
	}

	var aTableHtml = new Array();
	aTableHtml.push('<table>');

	if (datasource.data.caption)
	{
		aTableHtml.push('<caption>');
		var capDtaSrc = datasource.data.caption.datasource;
		var datatype = capDtaSrc.datatype;
		if (! datatype)
			datatype = datasourceType(capDtaSrc.data);
		if (datatype == DS_DTATYP_VALUE)
			aTableHtml.push(capDtaSrc.data);
		else if (datatype == DS_DTATYP_ARRAY)
		{
			// Array stuff ...
		}
		// .... etc ...
		aTableHtml.push('</caption>');
	}

	if (datasource.data.thead)
	{
		aTableHtml.push('<thead>');
		var theadDtaSrc = datasource.data.thead.datasource;
		var datatype = theadDtaSrc.datatype;
		if (! datatype)
			datatype = datasourceType(theadDtaSrc.data);
		//this.thead.datasource = theadDtaSrc;
		// The data must be a 2-dimensional array because Table header may contain multiple rows
		if	((datatype == DS_DTATYP_ARRAY) && theadDtaSrc.data.length)
		{
			var aaThead;
			// Check for an array initialiser
			var theadSource = arrayInitialiser(theadDtaSrc.data);

			if (! (theadSource [0] instanceof Array))
				// Force aaThead to be 2-dim row array with 1 child row array
				aaThead = [ theadSource ];
			else
				aaThead = theadSource;

			for (var row = 0; row < aaThead.length; row ++)
			{
				aTableHtml.push('<tr>');
				for (var col = 0; col < aaThead [row].length; col ++)
				{
					if (aaThead [row][col])
						aTableHtml.push('<th>' + aaThead [row][col] + '</th>');
					else
						aTableHtml.push('<th>&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp</th>');
				}
				aTableHtml.push('</tr>');
			}
		}
		// else
		// .... object, etc ...
		aTableHtml.push('</thead>');
	}

	if (datasource.data.tbody)
	{
		aTableHtml.push('<tbody>');
		var tbodyDtaSrc = datasource.data.tbody.datasource;
		var datatype = tbodyDtaSrc.datatype;
		if (! datatype)
			datatype = datasourceType(tbodyDtaSrc.data);
		// The data must be a 2-dimensional array of row arrays
		if	((datatype == DS_DTATYP_ARRAY) && tbodyDtaSrc.data.length)
		{
			var aaTbody;
			// Check for an array initialiser
			var tbodyData = arrayInitialiser(tbodyDtaSrc.data);

			if (! (tbodyData [0] instanceof Array))
				// Force aaTbody to be 2-dim row array with 1 child row array
				aaTbody = [ tbodyData ];
			else
				aaTbody = tbodyData;

			for (var row = 0; row < aaTbody.length; row ++)
			{
				aTableHtml.push('<tr>');
				for (col = 0; col < aaTbody [row].length; col ++)
				{
					var val, txtalign = { style:"" };
					if (aaTbody [row][col])
					{
						// Uncomment next 2 lines if formatting here using datasource.data.colfmt 
						//var val = Number(aaTbody [row][col]).toFixed(2);
						//if (isNaN(val))
							val = aaTbody [row][col];

							// See comments in makeFrmMainStylesheet() under '.frmtable td'
							if (bIE && typeof(val) == 'string' && val.length > 80)
								val = val.substring(0,80);

							if (this.format && colid && colid [col] && this.format [colid [col]])
								val = this.formatData(val, this.format [colid [col]], txtalign);
					}
					else
						val = '&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp';
					aTableHtml.push('<td' + txtalign.style + '>' + val + '</td>');
				}
				aTableHtml.push('</tr>');
			}
var x = 1;
		}
		// else
		// .... object, etc ...
		aTableHtml.push('</tbody>');
	}
	aTableHtml.push('</table>');

	this.elem.innerHTML = aTableHtml.join('');

	// If return value is true (this.datasource is object), used own this.datasource
	// If return value is false (this.datasource is null/undefined), used passed datasource arg
	return this.datasource;
}


// ************************ TableFromData *******************************************

function checkHeadType(table, bHeadFormatter, oFormatter, iType, iPrevHeadType)
{
	if (bHeadFormatter && oFormatter && oFormatter [iType] && iType != iPrevHeadType)
	{
		// Format provides oFormatter [iType] as the heading for iType, and the
		// current Type is not the previous heading to have been added.
		//
		// oFormatter [iType] is an array of strings. Add the heading row.
		table.addRow(table.tbody, oFormatter [iType], 'th', table.styleTr, table.styleTh);
		iPrevHeadType = iType;
	}
	return iPrevHeadType;
}


function checkNewRow(table, aRow, bRowFormatter, oFormatter, sValue)
{
	if (bRowFormatter && aRow.length && oFormatter && oFormatter [sValue])
	{
		// The property sValue exists in oFormatter. Add the current row and
		// start a new row.
		//
		// E.G in Element-type-based formatting sValue is current elem Type and
		// Format specifies Type as row end, or in Element-level-based formatting 
		// sValue is current elem Level and Format specifies Level as row end.
		var row;

		row = table.addRow(table.tbody, aRow, 'td', table.styleTr, table.styleTd);
		// Start a new row
		aRow.splice(0, aRow.length);
	}
}

// Possible rapid-access techniques for accessing the node in the Element object
// See: "C:/ispan/docs/Rapid-access to elem node corresponding to td element in Table"

function createRows(table, bRowFormatter, bHeadFormatter, oFormat, tbody, styleTr, 
						   styleTd, aRow, oElem, iLevel)
{
	// Traverse oElem constructing and inserting rows into tbody.
	//
	// oElem contains Element properties which are either Leaf or Compound Element Objects.
	//
	// The default definition of a row:
	// --------------------------------
	// A contiguous series of cells, each successive cell belonging to the 
	// same Compound Element as the previous cell, or to a Compound Element having a 
	// deeper level than the previous cell.
	//
	// This means that a new row is started whenever the end of an Compound Element is reached.
	//
	// Which also means that:
	// 1) If two or more Compound Elements, at the same level, follow one another and their 
	// cells are logically part of the same row, they will be in different rows.
	// 2) If a cell which is logically the end of a row is not the last element
	// in its Compound Element, a new row will not be started.
	//
	// The level of an element is the number of Compound Elements it is contained in
	// relative to the root oElem Compound Element. I.e. its nested depth.
	//
	// The Row Formatter definition of a row:
	// --------------------------------------
	// 
	//
	var iPrevHeadType = 0;
	args = { };

	++ iLevel;

	for (var prop in oElem)
	{
		var oDatum = oElem [prop];

		if (typeof(oDatum) != 'object' || ! set(oDatum ["T"]))
			continue;

		// A new row may be caused by the Type of a Compound or Leaf element. 
		// Add the currently accumulated row and start a new row.
		checkNewRow(table, aRow, bRowFormatter, oFormat.rowtype, oDatum ["T"]);
		// Add other Field-attribute based checkNewRow() here

		// Check whether there is a header for this Type that needs adding
		iPrevHeadType = checkHeadType(table, bHeadFormatter, oFormat.headtype, oDatum ["T"], iPrevHeadType);
		if (set(oDatum ["L"]))
			aRow.push(oDatum);
		else
			createRows(table, bRowFormatter, bHeadFormatter, oFormat, tbody, styleTr, 
				styleTd, aRow, oDatum, iLevel);
		// If we reach here, and it is the last 'prop in oElem', it has to be the end of 
		// the root compound elem, because of the basic structure of an Object or Element.
	}

	if (bRowFormatter)
	{
		// Element level based formatting
		checkNewRow(table, aRow, bRowFormatter, oFormat.rowlevel, iLevel);
		// Add other Non-Field-attribute based checkNewRow() here
	}
	else
	{
		// No Format Row specifiers. Compound elem end reached. Add the created row.
		checkNewRow(table, aRow, true, {"addit" : true}, "addit");
	}
}


TableFromData.inherits(Form); function TableFromData(Args)
{
	if (! set(Args))
		Args = {};

	this.template = {};

	this.constructorFunc = 'Table()';
//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
	this.template.constructorFunc = this.constructorFunc;
	this.template.constructorArgs = Args;

	Args.sTag = 'table';
	Form.call(this, Args);

	// FORMATTERS
	this.bRowFormatter = false;
	this.bHeadFormatter = false;
	this.oFormat = Args.oFormat;

	if (this.oFormat)
	{
		if (this.oFormat.rowtype || this.oFormat.rowlevel)
			this.bRowFormatter = true;
		if (this.oFormat.headtype || this.oFormat.headlevel)
			this.bHeadFormatter = true;
	}

	// CAPTION
	if (Args.sCaption)
	{
		this.caption = this.elem.createCaption();
		this.caption.innerHTML = Args.sCaption;
		if (this.styleCaption)
			this.setStyle(this.caption, this.styleCaption);
	}

	// HEADER
	if (Args.aHeader)
	{
		this.header = this.elem.createTHead();
		if (this.styleThead)
			this.setStyle(header, this.styleThead);
		for (var i = 0; i < Args.aHeader.length; i ++)
		{
			var saRow = Args.aHeader [i];
			this.addRow(this.header, saRow, 'th', this.styleHdTr, this.styleTh);
		}
	}

	// BODY
	this.tbody = document.createElement('tbody');
	this.elem.appendChild(this.tbody);
	if (this.styleTbody)
		this.setStyle(this.tbody, this.styleTbody);

	this.oData = {};
}


TableFromData.prototype.bindData = function(oData)
{
	this.datasource = oData;

	var aRow = new Array();
	
	createRows(this, this.bRowFormatter, this.bHeadFormatter, this.oFormat, this.tbody, 
		this.styleTr, this.styleTd, aRow, this.datasource, 0);

//	this.elem.data = this.datasource;
}


TableFromData.prototype.addRow = function(parentElem, saRow, sTag, styleHdTr, styleCell)
{
	// saRow is assumed to be an array of strings or an array of datasource reference objects
	var row = parentElem.insertRow(-1);
	if (styleHdTr)
		this.setStyle(row, styleHdTr);

	// If saRow is a string it must be treated as a single-cell row.
	if (typeof(saRow) == 'string')
	{
		var s = saRow;
		saRow = [ s ];
	}

	for	(var i = 0; i < saRow.length; i ++)
	{
		var cell, input;
		var oDatum = saRow [i];

		if (typeof(oDatum) == 'string')
		{
			if (sTag == 'td')
			{
				cell = row.insertCell(-1);
				input = document.createElement('input');
				input.value = oDatum;
				cell.appendChild(input);
			}
			else
			{
				cell = document.createElement(sTag);
				row.appendChild(cell);
				cell.innerHTML = oDatum;
			}
		}
		else
		{
			if (sTag == 'td')
			{
				cell = row.insertCell(-1);
				input = document.createElement('input');
//				input.value = oDatum.content;
				input.value = oDatum ["L"];
				cell.appendChild(input);
				if (this.datasource.frmOnChange)
					setEventListener(input, 'change', this.datasource.frmOnChange);

//try
//{
//	if (bOldIE)
//	{
		// Can't get attachEvent() in setEventListener() in main.js to work
		// Try this, but 'this' in cfdbOnChange() won't work
//		input.onchange = this.datasource.frmOnChange;
//debug('input.onchange: ' + input.onchange);
//	}
//	else
//		setEventListener(input, 'change', this.datasource.frmOnChange);
//}
//catch (e)
//{
//	debug('TableFromData.prototype.addRow, setEventListener e = ' + e);
//}

				// Data object link to DOM node
//				oDatum.dataobj.domnode = input;
				oDatum.domnode = input;
				// DOM node link to Data object
//				input.dataobj = oDatum.dataobj;
				input.datasource = oDatum;
				// In case the nodeName of dataref in datasource object is needed
				//cell [oDatum.elemid] = cell;
			}
			else
			{
				cell = document.createElement(sTag);
				row.appendChild(cell);
//				cell.innerHTML = oDatum.content;
				cell.innerHTML = oDatum ["L"];
				// Data object link to DOM node
				oDatum.domnode = cell;
				// DOM node link to Data object
				cell.datasource = oDatum;
			}
		}
		// DOM METHOD: (MUCH SLOWER than direct assignment to innerHTML)
		//cell.appendChild(document.createTextNode(oDatum));

		if (styleCell)
			this.setStyle(cell, styleCell);
	}
	return row;
}


TableFromData.prototype.getData = function()
{
	var xNewData = elemxMakeElemFromObj(this.oData, 0);
	
}


function frmGetTableFromElem(xTable)
{
	// Return an array with five elements:
	// Table style: a style typeset array initialiser string or a class string
	// Table datasource: a nested element containing compound (row) or leaf (data cell) elements
	// Table header: an array of column headers
	// Table caption: a string title for the table
	// Table format: a Format object element for the table
	var style, xTblData, xTblHeader, 
		sTblCaption, oTblFormat;
	var aTblHeader, aTblFormat;
	var iOffset = 0;
	var args = { };

	iOffset = elemxGet(xTable, iOffset, args);
	style = args.xChild;
	// style may be array or string
	if (style.charAt(0) == '[')
		style = eval('(' + style + ')');

	iOffset = elemxGet(xTable, iOffset, args);
	xTblData = args.xChild;
	iOffset = elemxGet(xTable, iOffset, args);
	xTblHeader = args.xChild;
	iOffset = elemxGet(xTable, iOffset, args);
	sTblCaption = args.xChild;
	iOffset = elemxGet(xTable, iOffset, args);
	oTblFormat = eval('(' + args.xChild + ')');

	iOffset = 0;
	while (iOffset = elemxGet(xTblHeader, iOffset, args))
	{
		if (! aTblHeader)
			aTblHeader = new Array();
		aTblHeader.push(args.xChild);
	}

	return new Array(style, xTblData, aTblHeader, sTblCaption, oTblFormat);
}


// ************************* Form Template *******************************


function createFormTemplates(parentFormTplate, parentForm)
{
	parentFormTplate.id = parentForm.id;
	parentFormTplate.value = parentForm.value;
	parentFormTplate.fitwidth = parentForm.fitwidth;
	parentFormTplate.label = parentForm.label;
	parentFormTplate.labelSide = parentForm.labelSide;
	parentFormTplate.labelStyle = parentForm.labelStyle;
	parentFormTplate.labelFormWidth = parentForm.labelFormWidth;
	parentFormTplate.labelFormHeight = parentForm.labelFormHeight;
	parentFormTplate.eventFuncs = parentForm.eventFuncs;
	parentFormTplate.groupForm = parentForm.groupForm;
	parentFormTplate.groupFuncs = parentForm.groupFuncs;
	parentFormTplate.buttonGroupForm = parentForm.buttonGroupForm;
	parentFormTplate.allowDeselect = parentForm.allowDeselect;
	parentFormTplate.sTip = parentForm.sTip;
	//parentFormTplate.rootForm = parentForm.rootForm;

	if (parentForm.style)
		parentFormTplate.style = parentForm.style;
	if (parentForm.stylesheet)
		parentFormTplate.stylesheet = createFormTplateStylesheet(parentForm);
	if (parentForm.datasource)
		parentFormTplate.datasource = parentForm.datasource;

	for (var prop in parentForm.childForms)
	{
		var form = parentForm.childForms [prop];
		if (! set(parentFormTplate.childForms))
			parentFormTplate.childForms = {};
		var formTplate = form.template;
//		formTplate.id = form.id;
		parentFormTplate.childForms [form.id] = formTplate;
		createFormTemplates(formTplate, form);
	}
}


function createFormTemplate(form)
{
	// Create and return an instance of a Form Template object.
	//
	// The arg 'form' is either a full Form object or a JSON Form Template String.
	//
	// If form is a JSON string, simply instantiate this form Template object from it.
	// 
	// If form is a full Form, it contains a template property which is an object 
	// containing the necessary properties to enable constructing an instance 
	// of the Form:

//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
	//		template.constructorFunc = <Form-subtype-constructor-function>';
	//		template.constructorArgs = <Form-subtype constructor args>;
	// NO> The template should only contain the two properties above because any other
	//	props can be got from the actual props in the actual form, eg stylesheet, datasource
	//
	// Every Form class initialises its .template property in its constructor.
	//
	// Starting at the root Form, multi-level nested Arrays of template objects are
	// created by adding an additional Array property, template.childForms, to
	// every template Form that has child Forms.
	//
	// The creation is done by walking the rootForm tree of childForms and building
	// an equivalent FormTemplate tree of child FormTemplates.

	var formTemplate;

	if (typeof(form) == 'string')
	{
		// form is a JSON Formset Template String
		try
		{
			formTemplate = JSON.parse(form, jsonFuncNameToFunc);
		}
		catch (e)
		{
			setStatus('createFormTemplate: Creation from JSON failed: ' + e, C_ERROR);
			return null;
		}
	}
	else
	{
		// form is a full Form object
		formTemplate = form.template;

//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
//if (formTemplate.constructorArgs)
//		formTemplate.constructorArgs.rootForm = true;
		createFormTemplates(formTemplate, form)
	}

//	formTemplate.toJson = toJsonFormTplate;

	formTemplate.toString = function() { return 'FormTemplate'; };
	return formTemplate;
}


//function toJsonFormTplate()
//{
//	// Convert this Formset Template object to JSON
//	var jFormTplate;
//
//	try
//	{
//		jFormTplate = JSON.stringify(this, jsonFuncNameToString);
//	}
//	catch (e)
//	{
//		setStatus('Conversion of Form Template object: toJsonFormTplate() failed: ' + e, C_ERROR);
//		jFormTplate = null;
//	}
//	return jFormTplate;
//}


function createFormTplateStylesheet(form)
{
	var stylesheet;
	if (form.stylesheet)
	{
		stylesheet = {};
		stylesheet.id = form.id;
		stylesheet.cssRules = [];
		for (var i = 0; i < form.stylesheet.cssRules.length; i ++)
		{
			stylesheet.cssRules [i] = {};
			stylesheet.cssRules [i].selectorText = form.stylesheet.cssRules [i].selectorText;
			stylesheet.cssRules [i].style = form.stylesheet.cssRules [i].style;
		}
	}
	return stylesheet;
}


//function createFormTplateDatasource(datasource)
//{
//	var formTplateDtasrc = {};
//
//	formTplateDtasrc.id = datasource.id;	
//	formTplateDtasrc.datatype = datasource.datatype;	
//	switch (formTplateDtasrc.datatype)
//	{
//		case 
//		formTplateDtasrc.data = datasource.;	
//	}
//
//	formTplateDtasrc.arraydim = datasource.arraydim;	
//	formTplateDtasrc.query = datasource.;	
//	formTplateDtasrc. = datasource.qrytype;	
//	return formTplateDtasrc;
//}


function loadFormFromTplateStore(id, formParent)
{
	var form, formTplate = formTemplates [id];

	if (! formTplate)
		return null;

//>>>TODONOW Surely this func should now just call createFormFromTemplate(formTplate)
//	instead of doing the switch() below and then calling createChildFormsFromTplate()?
//	The 'formParent.add/appendChild' bit could still be done, after calling createFormFromTemplate.

	// Create the Form from the template
	//switch(formTplate.constructorFunc)
	//{
	//	case 'Line': form = new Line(formTplate.constructorArgs); break;
	//	default: form = new Block(formTplate.constructorArgs); break;
	//}

//var t = typeof(formTplate.constructorFunc);
	if (typeof(formTplate.constructorFunc) != 'string')
		formTplate.constructorFunc = 'Block()';

//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
// Form constructors must not require an Arg object. Possibilities:
// 1) They are called with an optional arg which is a template object. 
//		This is really all an Arg object is, isn't it?
//		So the call below would be: form = new formTplate.constructorFunc(formTplate);
// 2) This func calls the appropriate constructor according to props in the template,
//		and then sets remaining form props from template props.
//		So the call below would be: form = new Field();
//		... form.stylesheet = formTplate.stylesheet; ... etc...
	switch (formTplate.constructorFunc)
	{
		case 'Form()':
				form = new Form(formTplate.constructorArgs);
				break;
		case 'Block()':
				form = new Block(formTplate.constructorArgs);
				break;
		case 'Line()':
				form = new Line(formTplate.constructorArgs);
				break;
		case 'Tabs()':
				form = new Tabs(formTplate.constructorArgs);
				break;
		case 'Tab()':
				form = new Tabs(formTplate.constructorArgs);
				break;
		case 'Field()':
				form = new Field(formTplate.constructorArgs);
				break;
		case 'Button()':
				form = new Button(formTplate.constructorArgs);
				break;
		case 'Text()':
				form = new Text(formTplate.constructorArgs);
				break;
		case 'Select()':
				form = new Select(formTplate.constructorArgs);
				break;
		case 'Table()':
				form = new Table(formTplate.constructorArgs);
				break;
	}
	//form.rootForm = true;


//------------------------------------------------------------------------------------------
		// First complete the form created by the preceding formTplate.constructorFunc() call
		// by adding the properties not included in the constructor Args.
		form.id = formTplate.id;
		if (set(formTplate.value && typeof(formTplate.value) == 'string'))
		{
			form.value = formTplate.value;
			// If value is 'datasource' the initial value will be set from
			// form.datasource.data in form.add().
			if (form.value.toLowerCase() != 'datasource')
				form.elem.innerHTML = form.value;
		}
		form.fitwidth = formTplate.fitwidth;
		form.label = formTplate.label;
		form.labelSide = formTplate.labelSide;
		form.labelStyle = formTplate.labelStyle;
		form.labelFormWidth = formTplate.labelFormWidth;
		form.labelFormHeight = formTplate.labelFormHeight;
		if (set(formTplate.eventFuncs))
		{
			form.eventFuncs = funcsFromTemplateFuncNames(formTplate.eventFuncs);
			form.setEventFuncs(form.eventFuncs);
		}
		form.groupForm = formTplate.groupForm;
		if (set(formTplate.groupFuncs))
			form.groupFuncs = funcsFromTemplateFuncNames(formTplate.groupFuncs);
		if (form.groupForm && form.groupForm.groupFuncs)
			setEventFuncs(form.groupForm.groupFuncs);
		form.buttonGroupForm = formTplate.buttonGroupForm;
		if (form.buttonGroupForm && form.buttonGroupForm.groupFuncs)
			setEventFuncs(form.buttonGroupForm.groupFuncs);
		form.allowDeselect = formTplate.allowDeselect;
		form.sTip = formTplate.sTip;
		form.format = formTplate.format;
//		form.template = formTplate;

		// Add the template style object properties into form.style and form.elem.style.
		// In the Form Creator, Form.style is populated after the Form has been
		// created with any constructor args style.
		if (formTplate.style)
		{
			if (! form.style)
				form.style = {};
			for (var prop in formTplate.style)
			{
				form.style [prop] = formTplate.style [prop];
				form.elem.style [prop] = formTplate.style [prop];
			}
		}

		if (formTplate.stylesheet)
			form.stylesheet = fcCreateStyleSheet(formTplate.stylesheet.id, formTplate.stylesheet.cssRules);

		if (formTplate.datasource)
			form.datasource = formTplate.datasource;

		// If this.value is set to 'datasource' and datasource was set in the Args, bind it immediately.
		if	(	form.value && form.value.toLowerCase() == 'datasource' && 
				form.datasource && form.datasource.data
			)
			form.bindData(form.datasource);

	// Not sure whether we should add the entire root Form to the formParent,
	// or just the root form.elem to the formParent.elem. The first way assumes
	// that the root Form is being added into an already created Form, but it
	// could be that it is just added to a bare element that is not part of the 
	// Form system. On the other hand we may want to load "sub" Forms into a
	// childForm of a parent Form. The simplest is to allow loadFormFromTplateStore()
	// to be called with formParent as a Form or an element.
	if (formParent instanceof Form)
		formParent.add(form);
	else if (formParent.appendChild)
		formParent.appendChild(form.elem);
	else
		return null;

	createChildFormsFromTplate(form, formTplate);

	return form;
}


function createChildFormsFromTplate(parentForm, parentFormTplate)
{
	for (var prop in parentFormTplate.childForms)
	{
		var formTplate, form;

		formTplate = parentFormTplate.childForms [prop];
		//switch(formTplate.constructorFunc)
		//{
		//	case 'Block': form = new Block(formTplate.constructorArgs); break;
		//	case 'Line': form = new Line(formTplate.constructorArgs); break;
		//	case 'Field': form = new Field(formTplate.constructorArgs); break;
		//	case 'Button': form = new Button(formTplate.constructorArgs); break;
		//	case 'Table': form = new Table(formTplate.constructorArgs); break;
		//	default: form = new Block(formTplate.constructorArgs); break;
		//}

//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
		if (typeof(formTplate.constructorFunc) != 'string')
			formTplate.constructorFunc = 'Block()';

//		form = new formTplate.constructorFunc(formTplate.constructorArgs);
		switch (formTplate.constructorFunc)
		{
			case 'Form()':
					form = new Form(formTplate.constructorArgs);
					break;
			case 'Block()':
					form = new Block(formTplate.constructorArgs);
					break;
			case 'Line()':
					form = new Line(formTplate.constructorArgs);
					break;
			case 'Tabs()':
					form = new Tabs(formTplate.constructorArgs);
					break;
			case 'Tab()':
					form = new Tabs(formTplate.constructorArgs);
					break;
			case 'Field()':
					form = new Field(formTplate.constructorArgs);
					break;
			case 'Button()':
					form = new Button(formTplate.constructorArgs);
					break;
			case 'Text()':
					form = new Text(formTplate.constructorArgs);
					break;
			case 'Select()':
					form = new Select(formTplate.constructorArgs);
					break;
			case 'Table()':
					form = new Table(formTplate.constructorArgs);
					break;
		}

//------------------------------------------------------------------------------------------
		// First complete the form created by the preceding formTplate.constructorFunc() call
		// by adding the properties not included in the constructor Args.
		form.id = formTplate.id;
		if (set(formTplate.value && typeof(formTplate.value) == 'string'))
		{
			form.value = formTplate.value;
			// If value is 'datasource' the initial value will be set from
			// form.datasource.data in form.add().
			if (form.value.toLowerCase() != 'datasource')
				form.elem.innerHTML = form.value;
		}
		form.fitwidth = formTplate.fitwidth;
		form.label = formTplate.label;
		form.labelSide = formTplate.labelSide;
		form.labelStyle = formTplate.labelStyle;
		form.labelFormWidth = formTplate.labelFormWidth;
		form.labelFormHeight = formTplate.labelFormHeight;
		if (set(formTplate.eventFuncs))
		{
			form.eventFuncs = funcsFromTemplateFuncNames(formTplate.eventFuncs);
			form.setEventFuncs(form.eventFuncs);
		}
		form.groupForm = formTplate.groupForm;
		if (set(formTplate.groupFuncs))
			form.groupFuncs = funcsFromTemplateFuncNames(formTplate.groupFuncs);
		if (form.groupForm && form.groupForm.groupFuncs)
			setEventFuncs(form.groupForm.groupFuncs);
		form.buttonGroupForm = formTplate.buttonGroupForm;
		if (form.buttonGroupForm && form.buttonGroupForm.groupFuncs)
			setEventFuncs(form.buttonGroupForm.groupFuncs);
		form.allowDeselect = formTplate.allowDeselect;
		form.sTip = formTplate.sTip;
		form.format = formTplate.format;

//		form.template = formTplate;

		// Add the template style object properties into form.style and form.elem.style.
		// In the Form Creator, Form.style is populated after the Form has been
		// created with any constructor args style.
		if (formTplate.style)
		{
			if (! form.style)
				form.style = {};
			for (var prop in formTplate.style)
			{
				form.style [prop] = formTplate.style [prop];
				form.elem.style [prop] = formTplate.style [prop];
			}
		}

		if (formTplate.stylesheet)
			form.stylesheet = fcCreateStyleSheet(formTplate.stylesheet.id, formTplate.stylesheet.cssRules);

		if (formTplate.datasource)
			form.datasource = formTplate.datasource;

		// If this.value is set to 'datasource' and datasource was set in the Args, bind it immediately.
//		if	(	form.value && form.value.toLowerCase() == 'datasource' && 
//				form.datasource && form.datasource.data
//			)
//			form.bindData(form.datasource);

		parentForm.add(form);

		createChildFormsFromTplate(form, formTplate);
	}
}


function createFormFromTemplate(formTplate)
{
	var form;

	//switch(formTplate.constructorFunc)
	//{
	//	case 'Block': form = new Block(formTplate.constructorArgs); break;
	//	case 'Line': form = new Line(formTplate.constructorArgs); break;
	//	case 'Field': form = new Field(formTplate.constructorArgs); break;
	//	case 'Button': form = new Button(formTplate.constructorArgs); break;
	//	case 'Table': form = new Table(formTplate.constructorArgs); break;
	//	default: form = new Block(formTplate.constructorArgs); break;
	//}
var t = typeof(formTplate.constructorFunc);

//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
	if (typeof(formTplate.constructorFunc) != 'string')
		formTplate.constructorFunc = 'Block()';
//	form = new formTplate.constructorFunc(formTplate.constructorArgs);
	switch (formTplate.constructorFunc)
	{
		case 'Form()':
				form = new Form(formTplate.constructorArgs);
				break;
		case 'Block()':
				form = new Block(formTplate.constructorArgs);
				break;
		case 'Line()':
				form = new Line(formTplate.constructorArgs);
				break;
		case 'Tabs()':
				form = new Tabs(formTplate.constructorArgs);
				break;
		case 'Tab()':
				form = new Tabs(formTplate.constructorArgs);
				break;
		case 'Field()':
				form = new Field(formTplate.constructorArgs);
				break;
		case 'Button()':
				form = new Button(formTplate.constructorArgs);
				break;
		case 'Text()':
				form = new Text(formTplate.constructorArgs);
				break;
		case 'Select()':
				form = new Select(formTplate.constructorArgs);
				break;
		case 'Table()':
				form = new Table(formTplate.constructorArgs);
				break;
	}

	return form;
}




function funcsFromTemplateFuncNames(aTplateFuncs)
{
	var aFuncs = [];

	for (var i = 0; i < aTplateFuncs.length; i ++)
	{
		if	(	aTplateFuncs [i] instanceof Array && 
				aTplateFuncs [i].length == 2 &&
				typeof(aTplateFuncs [i][0]) == 'string' &&
				typeof(aTplateFuncs [i][1]) == 'string'
			)
		{
			aFuncs [i] = [];
			aFuncs [i][0] = aTplateFuncs [i][0];
			aFuncs [i][1] = funcNameToFunc(aTplateFuncs [i][1]);
		}
		
	}
	return aFuncs;
}


var retRetLoadFormTplatesSvr = null;
//var sLoadFormTplatesSvrResult = '', iLoadFormTplatesSvrStatus = 0;

function retLoadFormTplatesSvr(sRetVal, iStatus)
{

	setStatus('');
	iLoadFormTplatesSvrStatus = iStatus;
	sLoadFormTplatesSvrResult = sRetVal;
	if (iStatus != RPC_OK)
		return retRetLoadFormTplatesSvr(iStatus, sRetVal);

	// Get the return object.
//	var obj = rpcGetRetVal(sRetVal, true, jsonFuncNameToFunc);
	var obj = rpcGetRetVal(sRetVal, true);
	var objType = TypeOf(obj);
	if (objType != 'object')
	{
		return retRetLoadFormTplatesSvr(-1, 'Expected Object, got Type: ' + objType);
	}

	// The returned obj has two properties: A result message and a 
	// FormTemplate Store object where the property names are 
	// FormTemplate IDs and each property value is a FormTemplate object. 
	// Add the FormTemplate objects into the local formTemplates store.

//	sLoadFormTplatesSvrResult = obj.result;

	for (var prop in obj.formTplates)
		formTemplates [prop] = obj.formTplates [prop];

	// Call our callback - set by the caller of loadFormTplatesSvr()
	retRetLoadFormTplatesSvr(iStatus, obj.result);
}


function loadFormTplatesSvr()
{
	var sFormIds = '';

	for (var i = 0; i < arguments.length; i ++)
	{
		if (i)
			sFormIds += ',';
		sFormIds += arguments [i];
	}
	rpc('loadFormTplates', [ sFormIds ], retLoadFormTplatesSvr);
}


function storeFormTemplateLocal(form)
{
	var formTemplate;

	if (typeof(form) == 'string' || form instanceof Form)
	{
		// 'form' is either a JSON Formset Template String or a full Form object. 
		// createFormTemplate() will convert either of these to a FormTemplate.
		formTemplate = createFormTemplate(form);
		if (! formTemplate)
			return null;
	}
	else
		// form assumed to be a FormTemplate
		formTemplate = form;

	// Add formTemplate to store. If formTemplate has no id, give it the number of
	// properties in the formTemplates object + 1.
	if (! formTemplate.id)
	{
		var i = 0;
		for (var x in formTemplates)
			++ i;
		formTemplate.id = 'f' + String(i + 1);
	}
	formTemplates [formTemplate.id] = formTemplate;
	return formTemplate;
}


function retStoreFormTplatesSvr(sRetVal, iStatus)
{
	setStatus('');
	var saRetVal = rpcGetRetVal(sRetVal);

	if (iStatus != RPC_OK)
	{
		setStatus(saRetVal [0], C_ERROR);
		return;
	}

	setStatus(saRetVal, C_SUCCESS);
}


function storeFormTemplatesSvr(formTemplates)
{
	var jFormTemplates;

	if (typeof(formTemplates) == 'string')
	{
		// formTemplates is already a JSON Templates object String
		jFormTemplates = formTemplates;
	}
	else
	{
		// formTemplates is a FormTemplates object whose property names are 
		// Form Template ids, and whose values are individual Form Templates.
		try
		{
			jFormTemplates = jsonFromObj(formTemplates, jsonFuncNameToString);
		}
		catch (e)
		{
			return false;
		}
	}
	// The rpc func storeFormTplates takes an array of FormTemplates. 
	// Store this single jFormTplate

	rpc('storeFormTplates', [ jFormTemplates ], retStoreFormTplatesSvr);
	return true;
}


function jsonFuncNameToString(key, value)
{
	var sName;

	//if (typeof(value) != 'function' && typeof(value) != 'object')
	//	return value;
	//if (value.toFuncName)
	//	sName = value.toFuncName();
	//else if (typeof(value) == 'object')
	//	return value;
	//else
		sName = funcNameToString(value);
	if (! sName)
	{
		if (typeof(value) == 'function')
		{
			return undefined;
		}
		else
			return value;
	}
	sName += '()';
	return sName;
}


function jsonFuncNameToFunc(key, value)
{
	var func, sName, iLen;

	if	(	typeof(value) == 'string' && (iLen = value.length) > 2 && 
			(value.substring(iLen -= 2) == '()') &&
			key != 'constructorFunc'
		)
	{
		sName = value.substring(0, iLen);
		func = funcNameToFunc(sName);
	}
	if (! func)
		func = value;
	return func;
}


// ************************* Datasource *******************************
var DS_LEN_ID = 40;
var DS_DTATYP_VALUE = 1;
var DS_DTATYP_ARRAY = 2;
var DS_DTATYP_FUNC = 4;
//var DS_DTATYP_NESTARRAY = 5;
var DS_DTATYP_TABLE = 6;
var DS_DTATYP_DTASRC = 7;
var DS_DTATYP_OBJECT = 8;
var DS_QRYTYP_SQL = 1;
var DS_QRYTYP_SQLPARAMS = 2;
var DS_QRYTYP_KEYWORD = 3;
var DATAPOS_START = 1;
var DATAPOS_END = 2;


function Datasource(datasource)
{
	// If datasource is defined it is an object created from an object initialise string

	// The id of the Form to which the datasource applies
	this.id = '';
	// The data, an actual instance, e.g. value, array, object
	this.data = '';
	// The type of the data, e.g. DS_DTATYP_ARRAY, DS_DTATYP_DTASRC
	this.datatype = 0;
	// The dimension of the array if datatype is DS_DTATYP_ARRAY
	this.arraydim = 0;
	this.query = '';
	this.qrytype = 0;

	if (datasource)
	{
		if (set(datasource.id))
			this.id = datasource.id;
		if (set(datasource.data))
			this.data = datasource.data;
		if (set(datasource.datatype))
			this.datatype = datasource.datatype;
		if (set(datasource.arraydim))
			this.arraydim = datasource.arraydim;
		if (set(datasource.query))
			this.query = datasource.query;
		if (set(datasource.qrytype))
			this.qrytype = datasource.qrytype;
	}

	this.toJson = toJsonDatasource;
	this.toString = function() { return 'Datasource'; };
}


function createDatasource(sJson)
{
	var datasource;

	if (! set(sJson) || typeof(fset) != 'string')
		return null;

	try
	{
		datasource = JSON.parse(sJson);
	}
	catch (e)
	{
		setStatus('createDatasource: Creation from JSON failed: ' + e, C_ERROR);
		return null;
	}

	datasource.toJson = toJsonDatasource;
	datasource.toString = function() { return 'Datasource'; };
	return datasource;
}


function datasourceType(ds)
{
	var type;

	if (typeof(ds) == 'function')
		type = DS_DTATYP_FUNC;
	else if (typeof(ds) == 'object')
	{
		if (ds instanceof Array)
			type = DS_DTATYP_ARRAY;
		else
			type = DS_DTATYP_OBJECT;
	}
	else
		type = DS_DTATYP_VALUE;
	return type;
}


function toJsonDatasource()
{
	// Convert this Datasource object to JSON
	var jDatasource;

	try
	{
		jDatasource = JSON.stringify(this);
	}
	catch (e)
	{
		setStatus('Conversion of Datasource object: toJsonDatasource() failed: ' + e, C_ERROR);
		jDatasource = null;
	}
	return jDatasource;
}


function arrayInitialiser(array, initval)
{
	// If 'array' is an Array of N dimensions, and every dimension, except the innermost, 
	// consists of a single Array element, and the 1st element of the innermost array is 
	// the keyword string '_arrayinit_' followed by N numeric elements 'n', create a new 
	// array of N dimensions, with each nested array having the number of elements of the 
	// corresponding 'n'. There must be N 'n' elements.
	//
	// If any of these conditions is not met, return the passed arg unchanged.
	//
	// Example 2-dimensional 'array' arg: [['_arrayinit_', n, n]]
	//
	// where the dimensions of the array is given by the number of n elements, and each n
	// is the number of elements in that dimension.
	//
	// Initialise the innermost array's elements as strings with the initval arg.
	var aArray, aInner = array, iN = 1, iDim1, iDim2;

	if	(! (array instanceof Array) || array.length < 1)
		return array;
	aInner = array;
	while (aInner [0] instanceof Array)
	{
		if (aInner.length != 1)
			return array;
		aInner = aInner [0]
		++ iN;
	}
	if	(	aInner.length != 1 + iN ||
			aInner [0].toLowerCase() != '_arrayinit_' ||
			isNaN(iDim = Number(aInner [1]))
		)
		return array;

	// aArray is 1-dimension
	aArray = new Array(iDim);

	if (aInner.length > 2 && ! isNaN(iDim2 = Number(aInner [2])))
	{
		// aArray is 2-dimension
		var	i, iDim3;

		for (i = 0; i < aArray.length; i ++)
		{
			aArray [i] = new Array(iDim2);
			if (aInner.length > 3 && ! isNaN(iDim3 = Number(aInner [3])))
			{
				// aArray is 3-dimension
				var j;

				for (j = 0; j < aArray [i].length; j ++)
				{
					aArray [i][j] = new Array(iDim3);
					if (set(initval))
					{
						var k;
						for (k = 0; k < aArray[i][j].length; k ++)
							aArray [i][j][k] = initval;
					}
				}
			}
			else if (set(initval))
			{
				var j;
				for (j = 0; j < aArray [i].length; j ++)
					aArray [i][j] = initval;
			}
		}
	}
	else if (set(initval))
	{
		var i;
		for (i = 0; i < aArray.length; i ++)
			aArray [i] = initval;
	}

	return aArray;
}


function ca(a, n)
{
	var i;

	for (i = 0; i < a.length; i ++)
		a [i] = new Array(n);

}

var retRetLoadDatasrcSvr = null;
//var sLoadDatasrcSvrResult = '', iLoadDatasrcSvrStatus = 0;

function retLoadDatasrcSvr(sRetVal, iStatus)
{
	setStatus('');
//	iLoadDatasrcSvrStatus = iStatus;
//	sLoadDatasrcSvrResult = sRetVal;
	if (iStatus != RPC_OK)
		return retRetLoadDatasrcSvr(iStatus, sRetVal);

	// Get the return object.
	var objDatasource = rpcGetRetVal(sRetVal, true, jsonFuncNameToFunc);
	var objType = TypeOf(objDatasource);
	if (objType != 'object')
		return retRetLoadDatasrcSvr(-1, 'Expected Object, got Type: ' + objType);

	// Call our callback (set by the caller of loadDatasource()) with the Datasource.
	retRetLoadDatasrcSvr(iStatus, objDatasource);
}


function loadDatasource(datasource)
{
	var jDatasource = datasource.toJson();
	rpc('loadDatasource', [ jDatasource ], retLoadDatasrcSvr);
}


// **************************** Form Creator ************************************

var frmMainStylesheet;
var selectedToolId;
var panFcStatus;
var panStyleTool;
var panTableElems;
var blkFormCreator;
var panBody;
var panRootScroll;
var blkFcRootForm;
var fldFormName;
var borderTool;
var sizeTool;
var fontTool;
var tableTool;
var butPointer;
var butTableTool;
var butNewForm, butOpenForm, butSaveForm, butDeleteForm, butClearForm;
var bFormModified = false;
var bClearClicked = false, bDeleteClicked = false;
var styleTargetForm;
var styleTargetElem;
var outlineElem;
var outlineBorderWidth = 2;
var sStylesheetSelector;
var butWidth, butStyle, butColor, butMargin, butPadding;
var butTable, butCaption, butThead, butTbody, 
	butTfoot, butTr, butTd, butTh;
var fldBackColour;
var fldForeColour;
var fldBordColor;
var butSideTop, butSideRig, butSideBot, butSideLef, butSideAll;
var selFamily
var labFontSizePercent, butStyleFontBold, butStyleFontItalic, butStyleFontUnder;
var fldDatsrcId;
var saFontFamily = [
	'Arial',
	'Courier',
	'Fixedsys',
	'Gothic',
	'Lucida',
	'MS Sans Serif',
	'Modern',
	'Roman',
	'Script',
	'Small Fonts',
	'System',
	'Tahoma',
	'Terminal',
	'Times New Roman',
	'Verdana'
];
var testDatasourceTable = 
{
	data:
	{
		colid: [ 'sampcol1','sampcol2' ],
		caption: { datasource: { data: 'Table Caption' } },
		thead: { datasource: { data: ['Col 1 heading','Col 2 heading'] } },
		tbody: { datasource: { data: [  ['Col 1 data','Col 2 data' ] ] } }
	},
	datatype: DS_DTATYP_TABLE
};


function fcInit()
{
	// Initialise the form system
	frmInit();
}


function createOutlineElem()
{
	// TODO >>> Use the CSS outline property: "outline: 3px solid #FFFF33;" if IE8
	// is detected. All other browsers except IE 4-7 support it.
	try
	{
		outlineElem = document.createElement('div');
		outlineElem.style.position = 'absolute';
		outlineElem.style.border = outlineBorderWidth + 'px solid #FFFF33';
		outlineElem.style.backgroundColor = 'transparent';
		elemStore.appendChild(outlineElem);
	}
	catch(e)
	{
		outlineElem = null;
	}
}


function bindTestData(form)
{
	var testDatasource;

	if (form instanceof Table)
		testDatasource = testDatasourceTable;
	if (testDatasource)
	{
		outlineForm(form, false);

//		form.datasource = testDatasource;
		form.bindData(testDatasource);

		outlineForm(form, true);
	}
}


function getComputedStylePx(elem, property, curStyleVal)
{
	var pixelSize;

	if (document.defaultView && document.defaultView.getComputedStyle)
	{
		property = cssPropSyntax(property);
		var computedStyle = document.defaultView.getComputedStyle(elem, null);
		pixelSize = computedStyle.getPropertyValue(property);
		pixelSize = parseInt(pixelSize);
	}
	else
	{
		if (curStyleVal && curStyleVal == 'auto')
		{
			// CSSStyle.getStyleSize can't handle 'auto'. Use clientWidth or clientHeight
			// but need to subtract padding which is included in clientWidth/Height.
			if (property == 'width')
			{
				var padding = CSSStyle.getStyleSize(elem, 'padding-left').px;
				if (! set(padding))
					padding += CSSStyle.getStyleSize(elem, 'padding-right').px;
				if (! set(padding))
					padding = 0;

				pixelSize = elem.clientWidth - padding;
			}
			else if (property == 'height')
			{
				var padding = CSSStyle.getStyleSize(elem, 'padding-top').px;
				if (! set(padding))
					padding += CSSStyle.getStyleSize(elem, 'padding-bottom').px;
				if (! set(padding))
					padding = 0;

				pixelSize = elem.clientHeight - padding;
			}
		}
		else
			pixelSize = CSSStyle.getStyleSize(elem, property).px;
	}
	if (typeof(pixelSize) != 'number')
		pixelSize = 0;
	return pixelSize;
}


function getDeclarededStyleCss(elem, property)
{
	var sCssValue;

	if (elem.currentStyle)
	{
		sCssValue = elem.currentStyle[domPropSyntax(property)];
	}
	else
	{
		// First try elem.style
		sCssValue = elem.style [domPropSyntax(property)];
		// Next try getComputedStyle(), but only for non-pixel values
		if	(	(! sCssValue || ! sCssValue.length) &&
				document.defaultView && document.defaultView.getComputedStyle
			)
		{
			var computedStyle = document.defaultView.getComputedStyle(elem, null);
			sCssValue = computedStyle.getPropertyValue(cssPropSyntax(property));
			if (sCssValue && sCssValue.substring(sCssValue.length - 2) == 'px')
				// Ignore px values, they may have been converted from the declared style
				// unit, which is what we want we want.
				sCssValue = null;
		}
		// If property is not set in elem.style, try document stylesheets for elem.className
		if (! sCssValue || ! sCssValue.length)
			sCssValue = fcGetStylePropertyFromStore(property, dotPrefix(elem.className), null, false);
	}
	if (! sCssValue)
		sCssValue = '';
	return sCssValue;
}


function getPixLen(length, fontsize)
{
	var iUnit;
	if ((iUnit = length.indexOf('px')) != -1)
		return Number(length.substring(0, iUnit));
	else if ((iUnit = length.indexOf('em')) != -1)
		return Number(length.substring(0, iUnit) * fontsize);
	else
		return 0;
}


function getLengthValUnit(sLength)
{
	var ValUnit = { val: null, unit: null };
	var i = sLength.search(/[^0-9.-]/);
	if (i)
	{
		ValUnit.val = sLength.substring(0,i);
		ValUnit.unit = sLength.substring(i);
	}
	else
		ValUnit.unit = sLength;
	return ValUnit;
}


function changeLength(elem, styleProp, changeVal, changeUnit, curValPx, initVal)
{
	// Change the value of elem.style [styleProp] by the amount specified in 
	// changeVal and changeUnit. If the curValPx arg is set, use that for the
	// pixel value to change rather than the pixel value of elem.style [styleProp]. 
	// If initVal is set and elem.style [styleProp] is not set, use initVal as the 
	// current property value/unit.
	//
	// The way that changeVal is applied depends on whether the unit in 
	// elem.style [styleProp] is px, em, % or auto, and whether changeUnit is px or %.
	//
	// E.G:
	// curLength = 4px, changeVal = 2 changeUnit = px: newStyleVal = 6px.
	// curLength = 3em, changeVal = 150 changeUnit = %: newStyleVal = 4.5em.

	var curLength, newVal, newStyleVal, lenVal, lenUnit, iUnit; //, fontsizepx = 16;

	curLength = getDeclarededStyleCss(elem, styleProp);
//debug('changeLength styleProp [' + styleProp + '] changeVal [' + changeVal + '] changeUnit [' + changeUnit + '] curLength [' + curLength + ']');
	// Split curLength into lenVal and lenUnit
	if (! curLength.length || curLength == '0')
	{
		// Current style value is '' or '0'. If initVal is set use it.
		if (initVal)
			curLength = initVal;
		else
			curLength = '0px';
	}
	if ((iUnit = curLength.indexOf('px')) == -1)
	{
		if ((iUnit = curLength.indexOf('em')) == -1)
			if ((iUnit = curLength.indexOf('%')) == -1)
				if ((iUnit = curLength.indexOf('auto')) == -1)
					return;
	}
	if (iUnit)
	{
		lenVal = Number(curLength.substring(0, iUnit));
		lenUnit = curLength.substring(iUnit);
	}
	else
	{
		lenVal = 0;
		lenUnit = curLength;
	}
	changeVal = Number(changeVal);

	if (! set(curValPx))
	{
		// Get the px value curValPx from getDeclarededStyleCss() if possible, 
		// else, if style in not in px, from getComputedStylePx().
		if (lenUnit == '%' && changeUnit == '%')
			curValPx = null;
		else if (lenVal && lenUnit == 'px')
			// Use the px value obtained from getDeclarededStyleCss() if possible
			curValPx = lenVal;
		else
			// Use getComputedStylePx() to get px value
			curValPx = getComputedStylePx(elem, styleProp, curLength);
	}

	// Get newVal (px or em or %) from changeVal (px or %) and curValPx (px)
	// according to lenUnit (px or em or % or auto).
	if (changeUnit == 'px')
	{
		if (lenUnit == 'px')
			newVal = curValPx + changeVal;
		else if (lenUnit == 'em')
		{
//			newVal = (curValPx + changeVal) / fontsizepx;
			newVal = lenVal * ((curValPx + changeVal) / curValPx);
		}
		else if (lenUnit == '%')
//>>>TODO
return;
		else if (lenUnit == 'auto')
		{
			// curValPx must have been set by caller or derived above
			newVal = curValPx + changeVal;
			lenUnit = 'px';
		}
		else
			return;
	}
	else if (changeUnit == '%')
	{
		if (lenUnit == 'px')
			newVal = ((100 + changeVal) * curValPx) / 100;
		else if (lenUnit == 'em')
//			newVal = (((100 + changeVal) * curValPx) / 100) / fontsizepx;
			newVal = lenVal * (100 + changeVal) / 100;
		else if (lenUnit == '%')
			// Add the change % to the current %. E.g. 10% on 150% = 160%, not 165%.
			newVal = lenVal + changeVal;
		else if (lenUnit == 'auto')
		{
			// curValPx must have been set by caller or derived above
			newVal = (changeVal * curValPx) / 100;
			lenUnit = 'px';
		}
		else
			return;
	}
	else
		newVal = curValPx;
	if (typeof(newVal) != 'number' || newVal < 0)
		return;
	newStyleVal = newVal + lenUnit;
//debug('changeLength curValPx [' + curValPx + '] newStyleVal [' + newStyleVal + ']');

	if (sStylesheetSelector)
		addFormStylesheetRule(styleProp, newStyleVal);
	else
		styleTargetForm.addStyleRule(styleProp, newStyleVal);
	bFormModified = true;
}


function changeLengthKeepSize(styleProp, changeVal, changeUnit, initVal)
{
	// If elem.style.border-width/padding is changed, the size/position of the elem
	// will change unless the content size is adjusted. Save the current offsetWidth
	// or offsetHeight then, after the border-width or padding has been changed, 
	// adjust the content size by the change in the offsetWidth or offsetHeight.
	//
//>>> TODO Currently we simply reset the elem.style.width/height/margin in px, but we should try 
// and reset it in ems.
	var curwidth = styleTargetElem.offsetWidth;
	var curheight = styleTargetElem.offsetHeight;

	var saStyleProps;
	if (typeof(styleProp) == 'string')
		saStyleProps = [ styleProp ];
	else
		saStyleProps = styleProp;
	for (var i = 0; i < saStyleProps.length; i ++)
		changeLength(styleTargetElem, saStyleProps [i], changeVal, changeUnit, null, initVal);

	// Don't try and keep the curheight of a table caption. The browser ignores our newheight
	// value and does its own thing. E.g. adjusting border by 1px with curheight of 23px means 
	// we set newheight to 21px, and browser sets CSS height to 21px but chops computed style
	// of height to 13px! Why?
	if (styleTargetElem.nodeName == 'CAPTION')
		return;

	var newwidth = styleTargetElem.offsetWidth;
	var newheight = styleTargetElem.offsetHeight;
	var widthadjust, heightadjust;

	if (widthadjust = curwidth - newwidth)
		changeLength(styleTargetElem, 'width', widthadjust, 'px');
	if (heightadjust = curheight - newheight)
		changeLength(styleTargetElem, 'height', heightadjust, 'px');
}


function fcMakeCreatorStylesheet()
{
	// Create the Form Creator stylesheet
	var rule;
	var sheet = new FrmStyleSheet('frmfc');

	rule = new FrmCSSStyleRule('.fcTool:hover', { 'background-color': '#CAD9E8' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.fcTool table', { width: '100%' } );
	rule = new FrmCSSStyleRule('.fcTool table', { 'border-collapse': 'collapse', 'table-layout': 'fixed', 
		width: '100%' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.fcTool td', { height: '1em', border: '1px solid #284265' } );
	sheet.insertRule(rule);
	rule = new FrmCSSStyleRule('.fcPointer', { 'background-color': 'transparent' } );
	sheet.insertRule(rule);
//	rule = new FrmCSSStyleRule('.fcPointer:hover', { 'background-color': '#CAD9E8' } );
//	sheet.insertRule(rule);


	sheet.addStyleSheetToDoc();
}


StyleTool.inherits(Form); function StyleTool(Args)
{
	if (! set(Args))
		Args = {};

	this.template = {};

//ARGS TODONOW>>> get rid of template's constructorFunc and constructorArgs. Use actual form props.
	this.template.constructorFunc = StyleTool;
	this.constructorFunc = 'StyleTool()';
//	this.template.constructorArgs = Args;

	Args.sTag = 'div';
	Args.formClass = 'frmblock';
	if (! Args.style)
		Args.style = {};
	Args.style.height = 'auto';
	Args.style.width = 'auto';
	Args.style.margin = '0.2em 0 0 0.2em';
	Args.style.fontSize = '80%';

	Form.call(this, Args);

	var sTitle = ' ';
	if (set(Args.sTitle))
		sTitle = Args.sTitle;
	var labTitle = new Text( { value: sTitle } );
	this.add(labTitle);

	var panMainPanel;
	if (Args.mainType == 'line')
		panMainPanel = new Line();
	else
		panMainPanel = new Block( { style: { border: '1px solid #4878B6' } } );
	this.add(panMainPanel);
	this.main = panMainPanel;
}


var fcButtonId;
var changeTimer;


function changeSize()
{
	var changeVal = 1, changeUnit = 'px', length;
	var style = styleTargetElem.style;

	outlineForm(styleTargetForm, false);

	if (changeTimer)
		changeVal = 6;
	if (fcButtonId == 'sizwiddec' || fcButtonId == 'sizhigdec')
		changeVal = -changeVal;
	if (fcButtonId == 'sizwidinc' || fcButtonId == 'sizwiddec')
	{
		changeLength(styleTargetElem, 'width', changeVal, changeUnit, length);
		// If the form was created with 'fitwidth' set, unset it now that the width has
		// been set to a specific length.
		if (styleTargetForm.fitwidth)
			styleTargetForm.fitwidth = false;
	}
	else if (fcButtonId == 'sizhiginc' || fcButtonId == 'sizhigdec')
		changeLength(styleTargetElem, 'height', changeVal, changeUnit, length);

	outlineForm(styleTargetForm, true);
}


function fcSizeClick(ev, targetTool)
{
	ev = getEventProps(ev);
	// Increase/decrease the width/height of the currently selected form
	ev.stopPropagation();
	if (! panStyleTool.enabled)
		return;
	if (! set(targetTool))
		targetTool = ev.target.srcForm;

	fcButtonId = targetTool.id;

	if (ev.type == 'click')
	{
		if (changeTimer)
		{
			clearInterval(changeTimer);
			changeTimer = null;
		}
		else
			changeSize();
	}
	else if (ev.type == 'mouseup')
	{
		if (changeTimer)
		{
			clearInterval(changeTimer);
			changeTimer = null;
		}
	}
	else if (ev.type == 'mousedown')
	{
		changeTimer = setInterval(changeSize, 150);
	}
}


function fcColourClick(ev, targetTool)
{
	var sColour, sProperty;

	ev = getEventProps(ev);
	// Change the 'backgroundColor' or 'color' style of the currently selected form
	ev.stopPropagation();
	if (! panStyleTool.enabled)
		return;
	if (! set(targetTool))
		targetTool = ev.target.srcForm;

	switch (targetTool.id)
	{
		case 'stbackcol': 
			sColour = fldBackColour.elem.value;
			sProperty = 'backgroundColor';
			break;
		case 'stforecol': 
			sColour = fldForeColour.elem.value;
			sProperty = 'color';
			break;
		default: return;
	}

	if (sColour.indexOf(',') != -1)
	{
		if (sColour.substring(0,4) != 'rgb(')
			sColour = 'rgb(' + sColour + ')';
	}
	else if (sColour.indexOf('#') == -1)
	{
		if (sColour.search(/^[a-f0-9]{6}$/i) != -1)
			sColour = '#' + sColour;
	}

	if (sStylesheetSelector)
		addFormStylesheetRule(sProperty, sColour);
	else
		styleTargetForm.addStyleRule(sProperty, sColour);
	bFormModified = true;
}


function fcTextClick(ev, targetTool)
{
	ev = getEventProps(ev);
	// Change the text of the currently selected form
	ev.stopPropagation();
	if (! panStyleTool.enabled)
		return;
	if (! set(targetTool))
		targetTool = ev.target.srcForm;

	//switch (target Form type, eg Block, Button, etc. Also may be used to set the form.label)
	//{
	//	case '?': 
	//		sText = fldForeText.elem.value;
	//		styleTargetElem.innerHTML = sText;
	//		break;
	//	case '?': 
	//		.............
	//		break;
	//	default: return;
	//}
	styleTargetForm.value = fldForeText.elem.value;
	styleTargetElem.innerHTML = styleTargetForm.value;
	bFormModified = true;
}


function fcBorderWSCClick(ev, targetTool)
{
	var groupForm;

	ev = getEventProps(ev);
	ev.stopPropagation();
	if (! panStyleTool.enabled)
		return;
	if (! set(targetTool))
		targetTool = ev.target.srcForm;

	if (! toggleButtonState(targetTool))
		// Clicked button was already the currently selected button
		return;

	groupForm = targetTool.groupForm;
	if (groupForm)
	{
		// Shut the currently open style panel
		if (groupForm.openForm)
			groupForm.openForm.elem.style.display = 'none';
		// Open the newly selected style panel
		switch (targetTool.id)
		{
			case 'fcbutbordwid': 
			case 'fcbutbordmar': 
			case 'fcbutbordpad': 
				groupForm.openForm = borderTool.main.styleWidth;
				borderTool.main.styleWidth.elem.style.display = 'block';
				break;
			case 'fcbutbordsty': 
				groupForm.openForm = borderTool.main.styleStyle;
				borderTool.main.styleStyle.elem.style.display = 'block';
				break;
			case 'fcbutbordcol': 
				groupForm.openForm = borderTool.main.styleColor;
				borderTool.main.styleColor.elem.style.display = 'block';
				break;
		}
	}
}


function fcBorderWidthClick(ev, targetTool)
{
	// Increase/decrease the width of the currently selected borders or margin or padding
	// of the currently selected form
	var saStyleProps = [ ], changeVal = 1, changeUnit = 'px';

	ev = getEventProps(ev);
	ev.stopPropagation();
	if (! panStyleTool.enabled)
		return;
	if (! set(targetTool))
		targetTool = ev.target.srcForm;

	// Remove the highlight
	outlineForm(styleTargetForm, false);

	if (targetTool.id == 'borwiddec')
		changeVal = -changeVal;

	if (butWidth.state || butPadding.state)
	{
		if (butWidth.state)
		{
			if (butSideTop.state)
				saStyleProps.push('borderTopWidth');
			if (butSideRig.state)
				saStyleProps.push('borderRightWidth');
			if (butSideBot.state)
				saStyleProps.push('borderBottomWidth');
			if (butSideLef.state)
				saStyleProps.push('borderLeftWidth');
		}
		else if (butPadding.state)
		{
			if (butSideTop.state)
				saStyleProps.push('paddingTop');
			if (butSideRig.state)
				saStyleProps.push('paddingRight');
			if (butSideBot.state)
				saStyleProps.push('paddingBottom');
			if (butSideLef.state)
				saStyleProps.push('paddingLeft');
		}

		changeLengthKeepSize(saStyleProps, changeVal, changeUnit);
	}
	else if (butMargin.state)
	{
		if (butSideTop.state)
			saStyleProps.push('marginTop');
		if (butSideRig.state)
			saStyleProps.push('marginRight');
		if (butSideBot.state)
			saStyleProps.push('marginBottom');
		if (butSideLef.state)
			saStyleProps.push('marginLeft');

		for (var i = 0; i < saStyleProps.length; i ++)
		{
			var elem = styleTargetElem;

			// Table CSS box excludes caption, but we want caption to shift with table, so 
			// marginTop must be caption's, else table would separate from caption.
			if (styleTargetForm instanceof Table && styleTargetElem.nodeName == 'TABLE' && 
				styleTargetForm.caption)
			{
				if (saStyleProps [i] == 'marginTop')
					elem = styleTargetElem.caption;
			}
			changeLength(elem, saStyleProps [i], changeVal, changeUnit);
		}
	}

	// Reinstate the highlight
	outlineForm(styleTargetForm, true);
}


function fcBorderStyleClick(ev, targetTool)
{
	// Change the style of the currently selected borders of the currently selected form
	ev = getEventProps(ev);
	ev.stopPropagation();
	if (! panStyleTool.enabled)
		return;
	if (! set(targetTool))
		targetTool = ev.target.srcForm;

	if (! toggleButtonState(targetTool, '#9CAFC8'))
		// Clicked button was already the currently selected button
		return;

	var sStyle;
	switch (targetTool.id)
	{
		case 'borstysol': sStyle = 'solid'; break; 
		case 'borstydot': sStyle = 'dotted'; break;
		case 'borstydas': sStyle = 'dashed'; break;
		case 'borstygrv': sStyle = 'groove'; break;
		case 'borstyrid': sStyle = 'ridge'; break;
		case 'borstyins': sStyle = 'inset'; break;
		case 'borstyout': sStyle = 'outset'; break;
		case 'borstydbl': sStyle = 'double'; break;
	}


	if (sStylesheetSelector)
	{
		if (butSideTop.state)
			addFormStylesheetRule('borderTopStyle', sStyle);
		if (butSideRig.state)
			addFormStylesheetRule('borderRightStyle', sStyle);
		if (butSideBot.state)
			addFormStylesheetRule('borderBottomStyle', sStyle);
		if (butSideLef.state)
			addFormStylesheetRule('borderLeftStyle', sStyle);
	}
	else
	{
		if (butSideTop.state)
			styleTargetForm.addStyleRule('borderTopStyle', sStyle);
		if (butSideRig.state)
			styleTargetForm.addStyleRule('borderRightStyle', sStyle);
		if (butSideBot.state)
			styleTargetForm.addStyleRule('borderBottomStyle', sStyle);
		if (butSideLef.state)
			styleTargetForm.addStyleRule('borderLeftStyle', sStyle);
	}
	bFormModified = true;
}


function fcBorderColorClick(ev, targetTool)
{
	// Change the colour of the currently selected borders of the currently selected form
	ev = getEventProps(ev);
	ev.stopPropagation();
	if (! panStyleTool.enabled)
		return;
	if (! set(targetTool))
		targetTool = ev.target.srcForm;

	var sColour = fldBordColor.elem.value;

	if (sColour.indexOf('#') == -1)
		sColour = '#' + sColour;

	if (sColour.search(/^#[a-f0-9]{6}$/i) != -1)
	{
		if (sStylesheetSelector)
		{
			if (butSideTop.state)
				addFormStylesheetRule('borderTopColor', sColour);
			if (butSideRig.state)
				addFormStylesheetRule('borderRightColor', sColour);
			if (butSideBot.state)
				addFormStylesheetRule('borderBottomColor', sColour);
			if (butSideLef.state)
				addFormStylesheetRule('borderLeftColor', sColour);
		}
		else
		{
			if (butSideTop.state)
				styleTargetForm.addStyleRule('borderTopColor', sColour);
			if (butSideRig.state)
				styleTargetForm.addStyleRule('borderRightColor', sColour);
			if (butSideBot.state)
				styleTargetForm.addStyleRule('borderBottomColor', sColour);
			if (butSideLef.state)
				styleTargetForm.addStyleRule('borderLeftColor', sColour);
		}
		bFormModified = true;
	}
}


function fcBorderSideClick(ev, targetTool)
{
	ev = getEventProps(ev);
	ev.stopPropagation();
	if (! panStyleTool.enabled)
		return;
	if (! set(targetTool))
		targetTool = ev.target.srcForm;

	toggleButtonState(targetTool, '#9CAFC8');

	if (targetTool.id == 'fcbutbordall')
	{
		toggleButtonState(butSideTop, '#9CAFC8', targetTool.state);
		toggleButtonState(butSideRig, '#9CAFC8', targetTool.state);
		toggleButtonState(butSideBot, '#9CAFC8', targetTool.state);
		toggleButtonState(butSideLef, '#9CAFC8', targetTool.state);
	}
}


function fcFontFamilyClick(ev, targetTool)
{
	// Change the font family of the currently selected form
	ev = getEventProps(ev);
	ev.stopPropagation();
	if (! panStyleTool.enabled)
		return;
	if (! set(targetTool))
		targetTool = ev.target.srcForm;

	var sFontFamily = selFamily.elem.options[selFamily.elem.selectedIndex].text;

	if (sStylesheetSelector)
		addFormStylesheetRule('fontFamily', sFontFamily);
	else
		styleTargetForm.addStyleRule('fontFamily', sFontFamily);
	bFormModified = true;
}


function changeFontSizeReset(styleProp, changeVal, changeUnit, initVal)
{
	// If elem.style.width/height/margin is set in ems and the font-size is changed, 
	// the size/position of the elem will increase/decrease unless their em size is 
	// adjusted. Save the current width/height/margin in px then, after the font-size 
	// has been changed, calc their new em size from the saved px size.
	//
//>>> TODO Currently we simply reset the elem.style.width/height/margin in px, but we should try 
// and reset it in ems.
	var curwidpx, curwidem = getDeclarededStyleCss(styleTargetElem, 'width');
	if (curwidem.search(/em$/i) != -1)
		curwidpx = getComputedStylePx(styleTargetElem, 'width');
	var curhigpx, curhigem = getDeclarededStyleCss(styleTargetElem, 'height');
	if (styleTargetElem.style.height.search(/em$/i) != -1)
		curhigpx = getComputedStylePx(styleTargetElem, 'height');
	var curmartoppx, curmartopem = getDeclarededStyleCss(styleTargetElem, 'margin-top');
	if (styleTargetElem.style.marginTop.search(/em$/i) != -1)
		curmartoppx = getComputedStylePx(styleTargetElem, 'margin-top');
	var curmarrigpx, curmarrigwidem = getDeclarededStyleCss(styleTargetElem, 'margin-right');
	if (styleTargetElem.style.marginRight.search(/em$/i) != -1)
		curmarrigpx = getComputedStylePx(styleTargetElem, 'margin-right');
	var curmarbotpx, curmarbotem = getDeclarededStyleCss(styleTargetElem, 'margin-bottom');
	if (styleTargetElem.style.marginBottom.search(/em$/i) != -1)
		curmarbotpx = getComputedStylePx(styleTargetElem, 'margin-bottom');
	var curmarlefpx, curmarlefem = getDeclarededStyleCss(styleTargetElem, 'margin-left');
	if (styleTargetElem.style.marginLeft.search(/em$/i) != -1)
		curmarlefpx = getComputedStylePx(styleTargetElem, 'margin-left');

	changeLength(styleTargetElem, styleProp, changeVal, changeUnit, null, initVal);

	if (curwidpx)
		styleTargetElem.style.width = curwidpx + 'px';
	if (curhigpx)
		styleTargetElem.style.height = curhigpx + 'px';
	if (curmartoppx)
		styleTargetElem.style.marginTop = curmartoppx + 'px';
	if (curmarrigpx)
		styleTargetElem.style.marginRight = curmarrigpx + 'px';
	if (curmarbotpx)
		styleTargetElem.style.marginBottom = curmarbotpx + 'px';
	if (curmarlefpx)
		styleTargetElem.style.marginLeft = curmarlefpx + 'px';
}


function fcFontSizeClick(ev, targetTool)
{
	// Increase/decrease the fontsize of the currently selected form, and display the
	// new size as a percentage. 
	ev = getEventProps(ev);
	ev.stopPropagation();
	if (! panStyleTool.enabled)
		return;
	var changePerc = 10;
	if (! set(targetTool))
		targetTool = ev.target.srcForm;

	fcButtonId = targetTool.id;
	if (fcButtonId == 'fonsizdec')
		changePerc = -changePerc;

	// In Forms the fontSize, if it is set, is always expressed as a percentage.
	// Get the current elem.style.fontSize setting. If it is unset, set it to 100%.
	// Inc/dec the setting by 10%. 
	// If elem.style.width/height/margin is set in ems, the size/position of the elem will increase 
	// unless the em size is reduced. Save the current width/height/margin in px then, after 
	// the fontsize has been changed, calc the new em size from the saved px size.
	changeFontSizeReset('fontSize', changePerc, '%', '100%');

	styleTargetForm.fontsizepx = getComputedStylePx(styleTargetElem, 'font-size');

	labFontSizePercent.elem.innerHTML = styleTargetElem.style.fontSize;
}


function fcFontStyleClick(ev, targetTool)
{
	// Change the bold or italic or underline font style of the currently selected form
	var sStyleProp, sStyleVal;

	ev = getEventProps(ev);
	ev.stopPropagation();
	if (! panStyleTool.enabled)
		return;

	if (! set(targetTool))
		targetTool = ev.target.srcForm;

	// Restrict Underline to Text.
	//
	// Originally it was permissible to insert text into Blocks, Lines, etc,
	// instead of just Texts. This meant it was possible for parent Forms to have
	// innerHtml text, and also to have child Forms. So if the parent Form's font was 
	// changed in any way, so was the child's. This function was to enable that to be 
	// prevented. But because of CSS text-decoration propagation to all child elements, 
	// regardless of the child's current text-decoration (CSS spec 16.3.1). Now, text 
	// can only exist in Texts so, although Texts can potentially have child Forms, 
	// inheritance of the font style is not a problem.
	if	(targetTool.id == 'fonstyund' && ! (styleTargetForm instanceof Text))
		return;

	if (! toggleButtonState(targetTool, '#9CAFC8'))
		// Clicked button was already the currently selected button
		return;

	switch (targetTool.id)
	{
		case 'fonstybol': 
			sStyleProp = 'fontWeight'; 
			if (targetTool.state)
				sStyleVal = 'bold';
			else
				sStyleVal = 'normal';
			break; 
		case 'fonstyita': 
			sStyleProp = 'fontStyle'; 
			if (targetTool.state)
				sStyleVal = 'italic';
			else
				sStyleVal = 'normal';
			break; 
		case 'fonstyund': 
			sStyleProp = 'textDecoration'; 
			if (targetTool.state)
				sStyleVal = 'underline';
			else
				sStyleVal = 'none';
			break; 
	}

	if (sStylesheetSelector)
		addFormStylesheetRule(sStyleProp, sStyleVal);
	else
		styleTargetForm.addStyleRule(sStyleProp, sStyleVal);
	bFormModified = true;
}


function fcTableElemClick(ev, targetTool)
{
	// Change the style of an element type (tag) of the currently selected form
	sStylesheetSelector = null;
	if (! (styleTargetForm instanceof Table))
		return;
	ev = getEventProps(ev);
	ev.stopPropagation();
	if (! panStyleTool.enabled)
		return;
	if (! set(targetTool))
		targetTool = ev.target.srcForm;

	if (! toggleButtonState(targetTool, '#9CAFC8'))
		// Clicked button was already the currently selected button
		return;

	var sDescendantSelector, targElem;

	// Reset styleTargetElem, which may have been changed below earlier.
	styleTargetElem = styleTargetForm.elem;

	switch (targetTool.id)
	{
		case 'tabelemtab': sDescendantSelector = 'table';  break; 
		case 'tabelemcap': sDescendantSelector = 'caption'; 
			targElem = styleTargetElem.caption; break;
		case 'tabelemthd': sDescendantSelector = 'thead'; 
			targElem = styleTargetElem.tHead; break;
		case 'tabelemtbd': sDescendantSelector = 'tbody';
			targElem = styleTargetElem.tBodies [0]; break;
		case 'tabelemtft': sDescendantSelector = 'tfoot';
			targElem = styleTargetElem.tFoot; break;
		case 'tabelemtr': sDescendantSelector = 'tr'; break;
		case 'tabelemtd': sDescendantSelector = 'td'; break;
		case 'tabelemth': sDescendantSelector = 'th'; break;
	}

	if (sDescendantSelector)
		setStylesheetSelector(sDescendantSelector);
	if (targElem)
		styleTargetElem = targElem;

	// Set style tool properties to styleTargetElem's actual values
	setStyleToolProps();
}


function setDatsrcToolProps()
{
	var datasource;

	// TODONOW: The datasource.id property should be automatically assigned from the
	// Form.id property, either when Form.datasource is created or when it is actually 
	// saved in the Form template. So fldDatsrcId should not be in the Datasource tool,
	// but in some other, basic Form attributes tool, or even in a 'title' box on its 
	// own in the Style block. Actually the 'Style block' should be renamed the
	// 'Form Properties' block.
	fldDatsrcId.elem.value = styleTargetForm.id;
	if (fldDatsrcId.elem.value == 'undefined')
		fldDatsrcId.elem.value = '';

	if (styleTargetElem == styleTargetForm.elem && set(styleTargetForm.datasource))
		datasource = styleTargetForm.datasource;
	else
	{
		// The target elem is not a Form; it is a child element of a Form, whose 
		// datasource structure depends on the Form class.
		if (styleTargetForm instanceof Table)
		{
			if (styleTargetElem == styleTargetForm.caption.elem && 
					set(styleTargetForm.datasource.data.caption.datasource))
				datasource = styleTargetForm.datasource.data.caption.datasource;
			else if (styleTargetElem == styleTargetForm.thead.elem && 
					set(styleTargetForm.datasource.data.thead.datasource))
				datasource = styleTargetForm.datasource.data.thead.datasource;
			else if (styleTargetElem == styleTargetForm.tbody.elem && 
					set(styleTargetForm.datasource.data.tbody.datasource))
				datasource = styleTargetForm.datasource.data.tbody.datasource;
		}
		else
			return;
	}

	fldDatsrcSrctype.elem.value = datasource.datatype;
	if (fldDatsrcSrctype.elem.value == 'undefined')
		fldDatsrcSrctype.elem.value = '';
	fldDatsrcQrytype.elem.value = datasource.qrytype;
	if (fldDatsrcQrytype.elem.value == 'undefined')
		fldDatsrcQrytype.elem.value = '';
	fldDatsrcQuery.elem.value = datasource.query;
	if (fldDatsrcQuery.elem.value == 'undefined')
		fldDatsrcQuery.elem.value = '';
}


function fcDatsrcClick(ev, targetTool)
{
	// Set the Form's datasource properties to the values in stDatsrc
	var datasource;

	ev = getEventProps(ev);
	ev.stopPropagation();
	if (! panStyleTool.enabled)
		return;

	// var sDatsrcId = fldDatsrcId.elem.value;
	// This s/b done with a 'Show test data' button
	//if (! sDatsrcId || ! sDatsrcId.length)
	//{
	//	bindTestData(styleTargetForm);
	//	return;
	//}
	if (! fldDatsrcId.elem.value.length)
	{
		fcStatus('ID field empty', C_ERROR);
		return;
	}

	// Set the Form's id to this Id
	styleTargetForm.id = fldDatsrcId.elem.value.toLowerCase();
	// Redispaly in field in case original value was entered with uppercase
	fldDatsrcId.elem.value = styleTargetForm.id;

	if	(	fldDatsrcSrctype.elem.value.length ||
			fldDatsrcQrytype.elem.value.length ||
			fldDatsrcQuery.elem.value.length
		)
	{
		if (! set(styleTargetForm.datasource))
			styleTargetForm.datasource = {};

		// TODONOW: The datasource.id property should be automatically assigned from the
		// Form.id property, either when Form.datasource is created or when it is actually 
		// saved in the Form template. So fldDatsrcId should not be in the Datasource tool,
		// but in some other, basic Form attributes tool, or even in a 'title' box on its 
		// own in the Style block. Actually the 'Style block' should be renamed the
		// 'Form Properties' block.
		styleTargetForm.datasource.id = styleTargetForm.id;
	}
	else
		return;

	if (styleTargetElem == styleTargetForm.elem)
	{
		datasource = styleTargetForm.datasource;
	}
	else if (styleTargetElem == styleTargetForm.caption.elem)
	{
		if (! set(styleTargetForm.datasource.data.caption.datasource))
			styleTargetForm.datasource.data.caption.datasource = {};
		datasource = styleTargetForm.datasource.data.caption.datasource;
	}
	else if (styleTargetElem == styleTargetForm.thead.elem)
	{
		if (! set(styleTargetForm.datasource.data.thead.datasource))
			styleTargetForm.datasource.data.thead.datasource = {};
		datasource = styleTargetForm.datasource.data.thead.datasource;
	}
	else if (styleTargetElem == styleTargetForm.tbody.elem)
	{
		if (! set(styleTargetForm.datasource.data.tbody.datasource))
			styleTargetForm.datasource.data.tbody.datasource = {};
		datasource = styleTargetForm.datasource.data.tbody.datasource;
	}
	else
		return;

	datasource.datatype = fldDatsrcSrctype.elem.value;
	datasource.qrytype = fldDatsrcQrytype.elem.value;
	datasource.query = fldDatsrcQuery.elem.value;

	bFormModified = true;
}


//function fcCloseStyleToolsClick(ev, targetTool)
//{
//	ev = getEventProps(ev);
//	ev.stopPropagation();
//	if (! panStyleTool.enabled)
//		return;
//	closeStyleTools();
//}


function fcDelTargetFormClick(ev, targetTool)
{
	// Deletes the currently selected form
	ev = getEventProps(ev);
	ev.stopPropagation();
	if (! panStyleTool.enabled)
		return;
	if (! set(targetTool))
		targetTool = ev.target.srcForm;

	if (! styleTargetForm.parent)
		// Can't delete the rootForm
		return;

	var targetForm = styleTargetForm;
	//var targetFormParent = styleTargetForm.parent;
	//var targetFormElem = styleTargetElem;
	//var targetFormId = styleTargetForm.id;
	closeStyleTools();
	// Delete the target element
	targetForm.parent.elem.removeChild(targetForm.elem);
	// Delete the target form
	delete targetForm.parent.childForms[targetForm.id];
	bFormModified = true;
}


function makeSizeTool(panStyleTool)
{
	sizeTool = new StyleTool( { sTitle: 'Size', 
		groupFuncs: [['click', fcSizeClick ],['mousedown', fcSizeClick ],['mouseup', fcSizeClick ]] } );
	panStyleTool.add(sizeTool);

	// Width
	var panWidth = new Line( { style: { border: 'none', 'border-bottom': '1px solid #4878B6' } } );
	sizeTool.main.add(panWidth);
	var labTitle = new Text( { value: 'Width:', style: { width: '3em', 'margin': '0 0.5em' } } );
	panWidth.add(labTitle);
	var butWidthInc = new Button( { id: 'sizwidinc', groupForm: sizeTool, value: '+',
		style: { width: 'auto', height: 'auto' } } );
	panWidth.add(butWidthInc);
	var butWidthDec = new Button( { id: 'sizwiddec', groupForm: sizeTool, value: '-', 
		style: { width: 'auto', height: 'auto' } } );
	panWidth.add(butWidthDec);
	// Height
	var panHeight = new Line( { style: { border: 'none' } } );
	sizeTool.main.add(panHeight);
	var labTitle = new Text( { value: 'Height:', style: { width: '3em', 'margin': '0 0.5em' } } );
	panHeight.add(labTitle);
	var butHeightInc = new Button( { id: 'sizhiginc', groupForm: sizeTool, value: '+',
		style: { width: 'auto', height: 'auto' } } );
	panHeight.add(butHeightInc);
	var butHeightDec = new Button( { id: 'sizhigdec', groupForm: sizeTool, value: '-',
		style: { width: 'auto', height: 'auto' } } );
	panHeight.add(butHeightDec);
}


function makeBackgroundTool(panStyleTool)
{
	var  stBack = new StyleTool( { id: 'stback', sTitle: 'Background' } );
	panStyleTool.add(stBack);
	fldBackColour = new Field();
	stBack.main.add(fldBackColour);
	var butBackColour = new Button( { id: 'stbackcol', sTip: 'Change background colour', value: 'Apply',
		eventFuncs: [['click', fcColourClick ]], style: { width: 'auto', height: 'auto' } } );
	stBack.main.add(butBackColour);
}


function makeForegroundTool(panStyleTool)
{
	var  stFore = new StyleTool( { id: 'stfore', sTitle: 'Foreground' } );
	panStyleTool.add(stFore);
	fldForeColour = new Field( { style: { height: 'auto', 'font-size': '80%', width: '6em' } } );
	stFore.main.add(fldForeColour);
	var butForeColour = new Button( { id: 'stforecol', sTip: 'Change foreground colour', value: 'Colour',
		eventFuncs: [['click', fcColourClick ]], style: { width: '6em', height: 'auto', 'font-size': '80%' } } );
	stFore.main.add(butForeColour);
	fldForeText = new Field( { style: { width: '6em', height: 'auto', 'font-size': '80%' } } );
	stFore.main.add(fldForeText);
	var butForeText = new Button( { id: 'stforetext', sTip: 'Change foreground text', value: 'Text',
		eventFuncs: [['click', fcTextClick ]], style: { width: '6em', height: 'auto', 'font-size': '80%' } } );
	stFore.main.add(butForeText);
}


function makeBorderTool(panStyleTool)
{
	borderTool = new StyleTool( { id: 'stbord', sTitle: 'Border' } );
	panStyleTool.add(borderTool);

	var panBorder = new Line( { style: { border: 'none' } } );
	borderTool.main.add(panBorder);

	var panMargPad = new Line( { style: { border: 'none' } } );
	borderTool.main.add(panMargPad);

	// Width/Style/Colour buttons
	var panWidStyCol = new Block( { style: { border: 'none', margin: '0.1em' },
		groupFuncs: [['click', fcBorderWSCClick ]] } );
	panBorder.add(panWidStyCol);

	butWidth = new Button( { id: 'fcbutbordwid', value: 'Width', 
		groupForm: panWidStyCol, buttonGroupForm: panWidStyCol, toggle: true, 
		style: { width: '4em', height: 'auto', 'font-size': '80%', 'margin-top': '0.1em', padding: '0' } } );
	panWidStyCol.add(butWidth);
	butStyle = new Button( { id: 'fcbutbordsty', value: 'Style', 
		groupForm: panWidStyCol, buttonGroupForm: panWidStyCol, toggle: true, 
		style: { width: '4em', height: 'auto', 'font-size': '80%', 'margin-top': '0.1em', padding: '0' } } );
	panWidStyCol.add(butStyle);
	butColor = new Button( { id: 'fcbutbordcol', value: 'Colour', 
		groupForm: panWidStyCol, buttonGroupForm: panWidStyCol, toggle: true, 
		style: { width: '4em', height: 'auto', 'font-size': '80%', 'margin-top': '0.1em', padding: '0' } } );
	panWidStyCol.add(butColor);

	// Margin/Padding buttons
	butMargin = new Button( { id: 'fcbutbordmar', value: 'Margin', 
		groupForm: panWidStyCol, buttonGroupForm: panWidStyCol, toggle: true, 
		style: { width: '4em', height: 'auto', 'font-size': '80%', margin: '0.1em 0 0 0.3em', padding: '0' } } );
	panMargPad.add(butMargin);
	butPadding = new Button( { id: 'fcbutbordpad', value: 'Padding', 
		groupForm: panWidStyCol, buttonGroupForm: panWidStyCol, toggle: true, 
		style: { width: '4em', height: 'auto', 'font-size': '80%', margin: '0.1em 0 0 0.3em', padding: '0' } } );
	panMargPad.add(butPadding);

	// Property selection panel
	var panSelect = new Block( { style: { border: '1px solid #7398C9', margin: '0.1em' } } );
	panBorder.add(panSelect);

	// Width
	var panBordWidth = new Block( { style: { border: 'none' }, 
		groupFuncs: [['click', fcBorderWidthClick ]] } );
	panSelect.add(panBordWidth);
	borderTool.main.styleWidth = panBordWidth;
	var butWidthInc = new Button( { id: 'borwidinc', groupForm: panBordWidth, value: '+',
		style: { width: 'auto', height: 'auto' } } );
	panBordWidth.add(butWidthInc);
	var butWidthDec = new Button( { id: 'borwiddec', groupForm: panBordWidth, value: '-',
		style: { width: 'auto', height: 'auto' } } );
	panBordWidth.add(butWidthDec);
	panBordWidth.elem.style.display = 'block';
	// Select the Width button and open the Width panel
	fcBorderWSCClick(null, butWidth);
	//toggleButtonState(butWidth);
	//panWidStyCol.openForm = panBordWidth;

	// Style
	var panBordStyle = new Line( { style: { border: 'none', height: 'auto', overflow: 'auto' },
		groupFuncs: [['click', fcBorderStyleClick ]]} );
	panSelect.add(panBordStyle);
	borderTool.main.styleStyle = panBordStyle;
	var panBordStyleL = new Block( { style: { border: 'none', 'margin-left': '0.2em' } } ); 
	panBordStyle.add(panBordStyleL);
	var panBordStyleR = new Block( { style: { border: 'none', 'margin-left': '0.2em' } } ); 
	panBordStyle.add(panBordStyleR);
	var butStyleSol = new Button( { id: 'borstysol', 
		style: { border: 'none', height: '0.3em', width: '1.5em', 
		border: '1px solid', 'margin-top': '0.15em' }, toggle: true,
		groupForm: panBordStyle, buttonGroupForm: panBordStyle } );
	panBordStyleL.add(butStyleSol);
	var butStyleDot = new Button( { id: 'borstydot',
		style: { border: 'none', height: '0.3em', width: '1.5em', 
		border: '1px dotted', 'margin-top': '0.15em' }, toggle: true, 
		groupForm: panBordStyle, buttonGroupForm: panBordStyle } );
	panBordStyleL.add(butStyleDot);
	var butStyleDas = new Button( { id: 'borstydas',
		style: { border: 'none', height: '0.3em', width: '1.5em', 
		border: '1px dashed', 'margin-top': '0.15em' }, toggle: true, 
		groupForm: panBordStyle, buttonGroupForm: panBordStyle } );
	panBordStyleL.add(butStyleDas);
	var butStyleGrv = new Button( { id: 'borstygrv',
		style: { border: 'none', height: '0.3em', width: '0.9em', 
		border: '6px groove', 'margin-top': '0.15em' }, toggle: true, 
		groupForm: panBordStyle, buttonGroupForm: panBordStyle } );
	panBordStyleL.add(butStyleGrv);
	var butStyleRid = new Button( { id: 'borstyrid',
		style: { border: 'none', height: '0.3em', width: '0.9em', 
		border: '6px ridge', 'margin-top': '0.15em' }, toggle: true, 
		groupForm: panBordStyle, buttonGroupForm: panBordStyle } );
	panBordStyleL.add(butStyleRid);
	var butStyleIns = new Button( { id: 'borstyins',
		style: { border: 'none', height: '0.3em', width: '0.9em', 
		border: '6px inset', 'margin-top': '0.15em' }, toggle: true, 
		groupForm: panBordStyle, buttonGroupForm: panBordStyle } );
	panBordStyleR.add(butStyleIns);
	var butStyleOut = new Button( { id: 'borstyout',
		style: { border: 'none', height: '0.3em', width: '0.9em', 
		border: '6px outset', 'margin-top': '0.15em' }, toggle: true, 
		groupForm: panBordStyle, buttonGroupForm: panBordStyle } );
	panBordStyleR.add(butStyleOut);
	var butStyleDbl = new Button( { id: 'borstydbl',
		style: { border: 'none', height: '0.3em', width: '1.3em', 
		border: '3px double', 'margin-top': '0.15em' }, toggle: true, 
		groupForm: panBordStyle, buttonGroupForm: panBordStyle } );
	panBordStyleR.add(butStyleDbl);
	panBordStyle.elem.style.display = 'none';

	// Colour
	var panBordColor = new Block( { style: { border: 'none' } } );
	panSelect.add(panBordColor);
	borderTool.main.styleColor = panBordColor;
	fldBordColor = new Field();
	panBordColor.add(fldBordColor);
	var butBordColour = new Button( { sTip: 'Change the colour of the selected border(s)', value: 'Apply',
		eventFuncs: [['click', fcBorderColorClick ]], style: { width: 'auto', height: 'auto' } } );
	panBordColor.add(butBordColour);


	panBordColor.elem.style.display = 'none';

	// Side selection panel
	var panSides = new Block( { style: { border: '1px solid #7398C9', margin: '0.1em' },
		groupFuncs: [['click', fcBorderSideClick ]]} );
	panBorder.add(panSides);
	panBorder.styleSides = panSides;
	panSides.top = false;
	panSides.rig = false;
	panSides.bot = false;
	panSides.lef = false;

	butSideTop = new Button( { id: 'fcbutbordtop', groupForm: panSides, toggle: true,
		style: { width: '2em', height: '0.2em', backgroundColor: '#D0D0D0', margin: '0.2em 0.5em' } } );
	panSides.add(butSideTop);
	var panSideRL = new Line( { style: { border: 'none' } } );
	panSides.add(panSideRL);
	butSideLef = new Button( { id: 'fcbutbordlef', groupForm: panSides, toggle: true,
		style: { width: '0.2em', height: '2em', backgroundColor: '#D0D0D0', 
		margin: '0.1em 0 0.1em 0.2em' } } );
	panSideRL.add(butSideLef);
	butSideAll = new Button( { id: 'fcbutbordall', groupForm: panSides, toggle: true,
		style: { width: '0.5em', height: '0.5em', verticalAlign: 'middle', 
		backgroundColor: '#D0D0D0', margin: '0 0.3em' } } );
	panSideRL.add(butSideAll);
	butSideRig = new Button( { id: 'fcbutbordrig', groupForm: panSides, toggle: true,
		style: { width: '0.2em', height: '2em', backgroundColor: '#D0D0D0', margin: '0.1em 0' } } );
	panSideRL.add(butSideRig);
	butSideBot = new Button( { id: 'fcbutbordbot', groupForm: panSides, toggle: true,
		style: { width: '2em', height: '0.2em', backgroundColor: '#D0D0D0', margin: '0.2em 0.5em' } } );
	panSides.add(butSideBot);
	fcBorderSideClick(null, butSideAll);
}


function makeFontTool(panStyleTool)
{
	fontTool = new StyleTool( { sTitle: 'Font' } );
	panStyleTool.add(fontTool);

	// Size
	var panSize = new Line( { groupFuncs: [['click', fcFontSizeClick ]] } );
	fontTool.main.add(panSize);
	var labTitle = new Text( { value: 'Size:', style: { 'margin': '0 0.5em' } } );
	panSize.add(labTitle);
	var butSizeInc = new Button( { id: 'fonsizinc', groupForm: panSize, value: '+',
		style: { width: 'auto', height: 'auto' } } );
	panSize.add(butSizeInc);
	var butSizeDec = new Button( { id: 'fonsizdec', groupForm: panSize, value: '-',
		style: { width: 'auto', height: 'auto' } } );
	panSize.add(butSizeDec);
	labFontSizePercent = new Text( { value: '100%', style: { 'margin-left': '1em' } } );
	panSize.add(labFontSizePercent);

	// Style
	var panStyleTool = new Line( { groupFuncs: [['click', fcFontStyleClick ]] } );
	fontTool.main.add(panStyleTool);

	butStyleFontBold = new Button( { id: 'fonstybol', groupForm: panStyleTool, toggle: true, value: 'Bold',
		style: { backgroundColor: '#D0D0D0', margin: '0.1em 0.1em', width: 'auto', height: 'auto' } } );
	panStyleTool.add(butStyleFontBold);
	butStyleFontItalic = new Button( { id: 'fonstyita', groupForm: panStyleTool, toggle: true, value: 'Italic',
		style: { backgroundColor: '#D0D0D0', margin: '0.1em 0.1em', width: 'auto', height: 'auto' } } );
	panStyleTool.add(butStyleFontItalic);
	butStyleFontUnder = new Button( { id: 'fonstyund', groupForm: panStyleTool, toggle: true, value: 'Underline',
		style: { backgroundColor: '#D0D0D0', margin: '0.1em 0.1em', width: 'auto', height: 'auto' } } );
	panStyleTool.add(butStyleFontUnder);

	// Family
	selFamily = new Select( { options: saFontFamily, eventFuncs: [['change', fcFontFamilyClick ]] } );
	fontTool.main.add(selFamily);
}


function makeTableTool(panStyleTool)
{
	tableTool = new StyleTool( { id: 'sttable', sTitle: 'Table' } );
	panStyleTool.add(tableTool);

	var panTable = new Line( { style: { border: 'none' } } );
	tableTool.main.add(panTable);

	// Table element selection buttons
	panTableElems = new Block( { id: 'qazwsx', style: { border: 'none', margin: '0.1em' },
		groupFuncs: [['click', fcTableElemClick ]], allowDeselect: true } );
	panTable.add(panTableElems);

	var panSelectTabElem = new Line( { style: { border: 'none', height: 'auto', overflow: 'auto' } } );
	panTableElems.add(panSelectTabElem);
	var panSelectTabElemL = new Block( { style: { border: 'none', 'margin-left': '0.2em' } } ); 
	panSelectTabElem.add(panSelectTabElemL);
	var panSelectTabElemR = new Block( { style: { border: 'none', 'margin-left': '0.2em' } } ); 
	panSelectTabElem.add(panSelectTabElemR);

	var butTabElemTab = new Button( { id: 'tabelemtab', value: 'Table',
		style: { width: '3.5em', height: 'auto', 'font-size': '80%', 
		'margin-top': '0.1em', padding: '0' }, toggle: true,
		/*groupForm: panTableElems,*/ buttonGroupForm: panTableElems } );
	panSelectTabElemL.add(butTabElemTab);
	var butTabElemCap = new Button( { id: 'tabelemcap', value: 'Caption',
		style: { width: '3.5em', height: 'auto', 'font-size': '80%', 
		'margin-top': '0.1em', padding: '0' }, toggle: true,
		/*groupForm: panTableElems,*/ buttonGroupForm: panTableElems } );
	panSelectTabElemL.add(butTabElemCap);
	var butTabElemThd = new Button( { id: 'tabelemthd', value: 'THead',
		style: { width: '3.5em', height: 'auto', 'font-size': '80%', 
		'margin-top': '0.1em', padding: '0' }, toggle: true,
		/*groupForm: panTableElems,*/ buttonGroupForm: panTableElems } );
	panSelectTabElemL.add(butTabElemThd);
	var butTabElemTbd = new Button( { id: 'tabelemtbd', value: 'TBody',
		style: { width: '3.5em', height: 'auto', 'font-size': '80%', 
		'margin-top': '0.1em', padding: '0' }, toggle: true,
		/*groupForm: panTableElems,*/ buttonGroupForm: panTableElems } );
	panSelectTabElemL.add(butTabElemTbd);
	var butTabElemTft = new Button( { id: 'tabelemtft', value: 'TFoot',
		style: { width: '3.5em', height: 'auto', 'font-size': '80%', 
		'margin-top': '0.1em', padding: '0' }, toggle: true,
		/*groupForm: panTableElems,*/ buttonGroupForm: panTableElems } );
	panSelectTabElemR.add(butTabElemTft);
	var butTabElemTr = new Button( { id: 'tabelemtr', value: 'TR',
		style: { width: '3.5em', height: 'auto', 'font-size': '80%', 
		'margin-top': '0.1em', padding: '0' }, toggle: true,
		/*groupForm: panTableElems,*/ buttonGroupForm: panTableElems } );
	panSelectTabElemR.add(butTabElemTr);
	var butTabElemTd = new Button( { id: 'tabelemtd', value: 'TD',
		style: { width: '3.5em', height: 'auto', 'font-size': '80%', 
		'margin-top': '0.1em', padding: '0' }, toggle: true,
		/*groupForm: panTableElems,*/ buttonGroupForm: panTableElems } );
	panSelectTabElemR.add(butTabElemTd);
	var butTabElemTh = new Button( { id: 'tabelemth', value: 'TH',
		style: { width: '3.5em', height: 'auto', 'font-size': '80%', 
		'margin-top': '0.1em', padding: '0' }, toggle: true,
		/*groupForm: panTableElems,*/ buttonGroupForm: panTableElems } );
	panSelectTabElemR.add(butTabElemTh);
}


function makeDatasourceTool(panStyleTool)
{
	var  stDatsrc = new StyleTool( { id: 'stdatsrc', sTitle: 'Datasource' } );
	panStyleTool.add(stDatsrc);

	fldDatsrcId = new Field( { id: 'fcdatsrcid', label: 'ID:' } );
	stDatsrc.main.add(fldDatsrcId);
	fldDatsrcSrctype = new Field( { id: 'fcdatsrcsrctype', label: 'Src Type:' } );
	stDatsrc.main.add(fldDatsrcSrctype);
	fldDatsrcQrytype = new Field( { id: 'fcdatsrcqrytype', label: 'Qry Type:' } );
	stDatsrc.main.add(fldDatsrcQrytype);
	fldDatsrcQuery = new Field( { id: 'fcdatsrcquery', label: 'Query:' } );
	stDatsrc.main.add(fldDatsrcQuery);


	var butDatsrc = new Button( { id: 'stdatsrcid', sTip: 'Set Datasource Parameters', value: 'Set',
		eventFuncs: [['click', fcDatsrcClick ]], style: { width: 'auto', height: 'auto' } } );
	stDatsrc.main.add(butDatsrc);
}


function getStylePropVal(form, prop)
{
	// Get the value of a Form style that has been specifically set
	// in the Form Creator. That means looking first in the elem.style, 
	// then in the form.stylesheet.
	var value = form.elem.style [prop];
	if (! value.length && form.stylesheet)
	{
		var selector = '#' + form.id;
		//if (sStylesheetSelector)
		//	selector += ' ' + sStylesheetSelector;
		value = form.stylesheet.getRulePropertyValue(selector, prop);
	}
	if (value && value.length > 9 && value.substring(0,4) == 'rgb(')
	{
		var a = value.match(/rgb\(([0-9, ]+)\)/);
		if (a)
			value = a [1];
	}
	if (typeof(value) == 'undefined')
		value = '';
	return value;
}


function setStyleToolProps()
{
	// Set style tool properties to styleTargetElem's actual values

	fldBackColour.elem.value = 
		getStylePropVal(styleTargetForm, 'backgroundColor')
	fldForeColour.elem.value = 
		getStylePropVal(styleTargetForm, 'color')
	var value = getStylePropVal(styleTargetForm, 'fontWeight');
	toggleButtonState(butStyleFontBold, '#9CAFC8', (value == 'bold'));
	var value = getStylePropVal(styleTargetForm, 'fontStyle');
	toggleButtonState(butStyleFontItalic, '#9CAFC8', (value == 'italic'));
	var value = getStylePropVal(styleTargetForm, 'textDecoration');
	toggleButtonState(butStyleFontUnder, '#9CAFC8', (value == 'underline'));
	labFontSizePercent.elem.innerHTML = getStylePropVal(styleTargetForm, 'fontSize');
	//selFamily.elem.options[selFamily.elem.selectedIndex].text;

	// Datasource
	setDatsrcToolProps();
}


function openStyleTools(targetForm)
{
	if (panStyleTool.enabled)
		return;
	styleTargetForm = targetForm;
	styleTargetElem = styleTargetForm.elem;
	// Ungrey the style tools
	panStyleTool.setEnabled(true);
	// Restrict Underline to Text.
	if (styleTargetForm instanceof Text)
		butStyleFontUnder.setEnabled(true);
	else
		butStyleFontUnder.setEnabled(false);

	// Set style tool properties to styleTargetElem's actual values
	setStyleToolProps();
}


function closeStyleTools()
{
	if (! panStyleTool.enabled)
		return;

	// Set all style tool properties to defaults
	fldBackColour.elem.value = '';
	fldForeColour.elem.value = '';
	fldForeText.elem.value = '';
	fcBorderWSCClick(null, butWidth);
	fcBorderSideClick(null, butSideAll);
	if (! butSideAll.state)
		// If butSideAll is now off it needs to be clicked again
		fcBorderSideClick(null, butSideAll);
	toggleButtonState(butStyleFontBold, '#9CAFC8', false)
	toggleButtonState(butStyleFontItalic, '#9CAFC8', false)
	toggleButtonState(butStyleFontUnder, '#9CAFC8', false)
	labFontSizePercent.elem.innerHTML = '100%';
	if (panTableElems.selected)
		toggleButtonState(panTableElems.selected);
	sStylesheetSelector = null;

	panStyleTool.setEnabled(false);
	// Undo restrict Underline to Text.
	butStyleFontUnder.setEnabled(true);
	if (styleTargetForm)
	{
		greyRoundTargetForm(styleTargetForm, false);
		outlineForm(styleTargetForm, false);
		styleTargetForm = null;
		styleTargetElem = null;
	}

	if (butPointer)
	{
		butPointer.elem.style.border = 'none';
		butPointer.elem.style.backgroundColor = 'transparent';
	}
//>>>
}


function setStylesheetSelector(sSelector)
{
	if (! styleTargetForm)
		return;

// RFSS
	sStylesheetSelector = '#' + styleTargetForm.id;
	if (sSelector && sSelector.length)
		sStylesheetSelector += ' ' + sSelector;

	//var rule = blkFcRootForm.stylesheet.getRule(selectorText);
	//if (rule)
	//	formStylesheetRule = rule;
	//else
	//	formStylesheetRule = new FrmCSSStyleRule(selectorText);
}


function addFormStylesheetRule(sProperty, sValue)
{
	//// Add the rule into blkFcRootForm.stylesheet
	//formStylesheetRule.setProperty(sProperty, sValue);
	//if (! formStylesheetRule.bRuleInsertedInDom)
	//	blkFcRootForm.stylesheet.insertRule(formStylesheetRule);

// RFSS
// The rest of lines in func relate to 'styleTargetForm.stylesheet'. 
// So, yes, there must be a cascade of per-Form stylesheets, not just
// one rootForm stylesheet. Apart from later ss selectors overriding the
// rootForm selector, it means that Forms can be self-contained in their
// style, and used in different rootForms, or even be rootForms themselves.

	if (! sStylesheetSelector)
		return;

	// Create styleTargetForm.stylesheet if necessary and ensure it is in the DOM
	if (! styleTargetForm.stylesheet)
		styleTargetForm.stylesheet = new FrmStyleSheet(styleTargetForm.id);
	if (! styleTargetForm.stylesheet.bSheetAddedToDoc)
		styleTargetForm.stylesheet.addStyleSheetToDoc();

	// Add or update the rule in styleTargetForm.stylesheet
	var rule = styleTargetForm.stylesheet.getRule(sStylesheetSelector);
	if (! rule)
	{
		rule = new FrmCSSStyleRule(sStylesheetSelector);
		rule.style [domPropSyntax(sProperty)] = sValue;
		styleTargetForm.stylesheet.insertRule(rule);
	}
	//rule.setDeclaration(sProperty, sValue);
	rule.setProperty(sProperty, sValue);
var x = 0;
}


function fcNewFormClick(ev)
{
	// Create a new, blank Form
	ev = getEventProps(ev);
	ev.stopPropagation();
	fcStatus();
	var	sError = '';
	var rootFormId = fldFormName.elem.value;

	if (rootFormId == '')
		sError = 'Please enter a unique identifer in the';
	else if (rootFormId.length < 4 || rootFormId.length > 40)
		sError = 'There must be between 4 and 40 characters in the';
	else if (rootFormId.indexOf(' ') != -1)
		sError = 'There must not be any spaces in the';
	else if (rootFormId.search(/^[0-9A-Za-z.-_]+$/) == -1)
		sError = 'There are invalid characters in the';
	else if (formTemplates [rootFormId])
		sError = 'Form "' + rootFormId + '" already exists. Enter a new';
	if (sError != '')
	{
		fcStatus(sError + ' Form Name. Click the "New" button when ready.');
		return;
	}

	blkFcRootForm = new Block( { id: rootFormId, //rootForm: true,  
		eventFuncs: [['click', panRootClick]], classname: 'frmform' } );
	//blkFcRootForm.rootForm = true;
	panRootScroll.add(blkFcRootForm);
	panBody.setEnabled(true);
// RFSS
	blkFcRootForm.stylesheet = new FrmStyleSheet(blkFcRootForm.id);
	blkFcRootForm.stylesheet.addStyleSheetToDoc();

	fldFormName.setEnabled(false);
	butNewForm.setEnabled(false);
	butOpenForm.setEnabled(false);
	butSaveForm.setEnabled(true);
	butDeleteForm.setEnabled(false);
	butClearForm.setEnabled(true);
}

var openedFormId;

function fcOpenFormClick(ev)
{
	// Open an existing Form
	ev = getEventProps(ev);
	ev.stopPropagation();
	fcStatus();

	if (bFormModified)
	{
		fcPromptSave();
		return;
	}
	var id = fldFormName.elem.value;
	if (id == '')
	{
		fcStatus('Please enter an existing Form Name. Click the "Open" button when ready.');
		return;
	}

	// Load the Formset from the formTemplates store, or the server, 
	// and display in the Form page.
	openedFormId = id;
	if (formTemplates [id])
		fcLoadOpenedForm(RPC_OK);
	else
	{
		// Try server.
		// Set the callback for retLoadFormTplatesSvr
		retRetLoadFormTplatesSvr = fcLoadOpenedForm;
		loadFormTplatesSvr(id);
	}
}


function fcLoadOpenedForm(iStatus, sResult)
{
//	var sResult = sLoadFormTplatesSvrResult;
//	sLoadFormTplatesSvrResult = '';
//	if (iLoadFormTplatesSvrStatus != RPC_OK)
	if (iStatus != RPC_OK)
	{
		fcStatus('Error opening Form: ' + sResult, C_ERROR);
		openedFormId = null;
		return;
	}
		
	if (! openedFormId)
		return;
// RFSS
// loadFormFromTplateStore() creates the form.stylesheet 
	blkFcRootForm = loadFormFromTplateStore(openedFormId, panRootScroll.elem);
	if (! blkFcRootForm)
	{
		fcStatus('Form ' + openedFormId + ' not found. ' + sResult, C_ERROR);
		openedFormId = null;
		return;
	}

	panBody.setEnabled(true);
	fldFormName.setEnabled(false);
	butNewForm.setEnabled(false);
	butOpenForm.setEnabled(false);
	butSaveForm.setEnabled(false);
	butDeleteForm.setEnabled(true);
	butClearForm.setEnabled(true);
	openedFormId = null;
}


function fcSaveFormClick(ev)
{
	// Save the current Form
	ev = getEventProps(ev);
	ev.stopPropagation();
	fcStatus();

	var formTemplate = storeFormTemplateLocal(blkFcRootForm);
	var formTplates = {};
	formTplates [formTemplate.id] = formTemplate;
	storeFormTemplatesSvr(formTplates);
	bFormModified = false;

	fldFormName.setEnabled(false);
	butNewForm.setEnabled(false);
	butOpenForm.setEnabled(false);
	butSaveForm.setEnabled(false);
	butDeleteForm.setEnabled(true);
	butClearForm.setEnabled(true);
}


function fcDeleteFormClick(ev)
{
	// Delete a Form
	ev = getEventProps(ev);
	ev.stopPropagation();
	fcStatus();

	if (! bDeleteClicked)
	{
		fcStatus('This Form will be permanently removed, OK? Click "Delete" again to delete the Form.');
		bDeleteClicked = true;
		return;
	}

	// Delete the Form from the local and server FormTemplate stores
	delete formTemplates [blkFcRootForm.id];	

	bDeleteClicked = false;
	bFormModified = false;

	// Remove the Form from the Form Creator
	fcClearFormClick();

	panBody.setEnabled(false);
	fldFormName.setEnabled(true);
	fldFormName.elem.value = '';
	butNewForm.setEnabled(true);
	butOpenForm.setEnabled(true);
	butSaveForm.setEnabled(false);
	butDeleteForm.setEnabled(false);
	butClearForm.setEnabled(false);
}


function fcClearFormClick(ev)
{
	// Clear the Form
	ev = getEventProps(ev);
	ev.stopPropagation();
	fcStatus();

	if (bFormModified && ! bClearClicked)
	{
		fcStatus('The current Form needs saving. Click "Clear" again to continue without saving.');
		bClearClicked = true;
		return;
	}

	// Remove the Form from the Form Creator
	blkFcRootForm.deleteForm();
	
	bClearClicked = false;
	bFormModified = false;

	panBody.setEnabled(false);
	fldFormName.setEnabled(true);
	fldFormName.elem.value = '';
	butNewForm.setEnabled(true);
	butOpenForm.setEnabled(true);
	butSaveForm.setEnabled(false);
	butDeleteForm.setEnabled(false);
	butClearForm.setEnabled(false);
}


function fcStatus(sStatus)
{
	if (set(sStatus) && sStatus.length)
	{
		panFcStatus.elem.innerHTML = sStatus;
		panFcStatus.elem.style.display = 'block';
	}
	else
	{
		panFcStatus.elem.innerHTML = '';
		panFcStatus.elem.style.display = 'none';
	}
}


function FormCreator(parentElem)
{
	// TODO> FC was started as a static, global function with global variables so that they could
	// be accessed from outside the function. Make it an Object with member this.X properties, except
	// for necessarily global variables like the FC stylesheet in fcMakeCreatorStylesheet(), and
	// the formTemplates store.
	if (! parentElem)
		return null;

	// Create the Form Creator stylesheet
	fcMakeCreatorStylesheet();
	createOutlineElem();

	blkFormCreator = new Block( { id: 'fcmain', eventFuncs: [['mouseout', panMainMouseout ]] } );
	//blkFormCreator.rootForm = true;
	// Add blkFormCreator to DOM before any calls to Form.add()
	parentElem.appendChild(blkFormCreator.elem);

	var panBut = new Line( { id: 'fcbut', style: { height: '2em', backgroundColor: '#D8E3EE' } } );
	blkFormCreator.add(panBut);
	panFcStatus = new Text( { id: 'fcstatus', style: { height: '1em', color: '#B01616', 
		border: '1px solid', 'text-align': 'left' } } );
	blkFormCreator.add(panFcStatus);
	panFcStatus.shut();
	panStyleTool = new Line( { id: 'fcstyle', style: { height: 'auto', backgroundColor: '#F1F5F9' } } );
	blkFormCreator.add(panStyleTool);
	panBody = new Line( { id: 'fcbody', classname: '', style: { backgroundColor: '#FFFFFF' } });
	blkFormCreator.add(panBody);
	var panFormTool = new Block( { id: 'fctool', style: { height: '48em', width: '6em',
		borderRight: '1px solid #4878B6', borderBottom: '1px solid #4878B6', 
		borderTop: 'none', borderLeft: 'none', backgroundColor: '#F1F5F9' } });
	panBody.add(panFormTool);
	panRootScroll = new Block( { id: 'fcrootscroll', classname: '', style: { margin: '1em', overflow: 'auto', 
		border: 'none', backgroundColor: '#FFFFFF', maxWidth: '60em', maxHeight: '60em' } } );
	panBody.add(panRootScroll);

	// Buttons
	var labTitle = new Text( { id: 'fctitle', value: 'Form Creator', 
		style: { fontSize: '1.5em', color: '#3D6389', 'margin-top': '0.2em' }  } );
	panBut.add(labTitle);
	fldFormName = new Field( { id: 'fcformname', label: 'Form Name:', 
		style: { height: 'auto', width: '12em', 'margin-top': '0.3em', 'margin-left': '2em', 
		'margin-right': '2em' } } );
	panBut.add(fldFormName);
	butNewForm = new Button( { sTip: 'Create a new Form', value: 'New',
		eventFuncs: [['click', fcNewFormClick ]], 
		style: { verticalAlign: 'middle', marginTop: '0.1em', marginLeft: '1em', 
		padding: '0.1em', 'padding-left': '0.8em', width: '3em', height: 'auto' } } );
	panBut.add(butNewForm);
	butOpenForm = new Button( { sTip: 'Open an existing Form', value: 'Open',
		eventFuncs: [['click', fcOpenFormClick ]], 
		style: { verticalAlign: 'middle', marginTop: '0.1em', marginLeft: '1em', 
		padding: '0.1em', 'padding-left': '0.5em', width: '3em', height: 'auto' } } );
	panBut.add(butOpenForm);
	butSaveForm = new Button( { sTip: 'Save this Form', value: 'Save',
		eventFuncs: [['click', fcSaveFormClick ]], 
		style: { verticalAlign: 'middle', marginTop: '0.1em', marginLeft: '1em', 
		padding: '0.1em', 'padding-left': '0.5em', width: '3em', height: 'auto' } } );
	panBut.add(butSaveForm);
	butSaveForm.setEnabled(false);
	butDeleteForm = new Button( { sTip: 'Delete this Form', value: 'Delete',
		eventFuncs: [['click', fcDeleteFormClick ]], 
		style: { verticalAlign: 'middle', marginTop: '0.1em', marginLeft: '1em', 
		padding: '0.1em', 'padding-left': '0.2em', width: '3em', height: 'auto' } } );
	panBut.add(butDeleteForm);
	butDeleteForm.setEnabled(false);
	butClearForm = new Button( { sTip: 'Clear this Form', value: 'Clear',
		eventFuncs: [['click', fcClearFormClick ]], 
		style: { verticalAlign: 'middle', marginTop: '0.1em', marginLeft: '1em', 
		padding: '0.1em', 'padding-left': '0.2em', width: '3em', height: 'auto' } } );
	panBut.add(butClearForm);
	butClearForm.setEnabled(false);

	// Style Tools

	// Size
	makeSizeTool(panStyleTool);
	// Background
	makeBackgroundTool(panStyleTool);
	// Foreground
	makeForegroundTool(panStyleTool);
	// Border
	makeBorderTool(panStyleTool);
	// Font
	makeFontTool(panStyleTool);
	// Table
	makeTableTool(panStyleTool);
	// Datasource
	makeDatasourceTool(panStyleTool);
	// Close
	//butCloseStyleTools = new Button( { sTip: 'Finish styling this field', value: 'Close',
	//	eventFuncs: [['click', fcCloseStyleToolsClick ]], 
	//	style: { verticalAlign: 'middle', marginLeft: '1em', marginTop: '2em', width: 'auto', height: 'auto' } } );
	//panStyleTool.add(butCloseStyleTools);
	// Delete
	var butDelTargetForm = new Button( { sTip: 'Delete this field', value: 'Delete',
		eventFuncs: [['click', fcDelTargetFormClick ]], 
		style: { verticalAlign: 'middle', marginLeft: '1em', marginTop: '2em', width: 'auto', height: 'auto' } } );
	panStyleTool.add(butDelTargetForm);


	// Form Tools
	
	var labToolCenterer = new Text( { style: { textAlign: 'center', width: '6em' } } );
	panFormTool.add(labToolCenterer);

	butPointer = new Button( { id: 'fcpointer', classname: 'fcPointer', style: { height: '42px', 
		backgroundImage: 'url("/image/fcpointer.gif")', backgroundRepeat: 'no-repeat',
		backgroundPosition: 'center', width: '32px', border: 'none', margin: '1em auto' },
		sTip: 'Select an item in the Form', eventFuncs: [ ['click', butPointerClick ],
		['selectstart', noTextSelect],['mousedown', noTextSelect],
		['mouseover', fcHover],['mouseout', fcHover] ] } );
	panFormTool.add(butPointer);
	butPointer.hoverStyle = { backgroundColor: '#CAD9E8' };

	var butBlockTool = new Button( { id: 'fcpaneltool', classname: 'fcTool',  
		style: {height: '42px', width: '32px', border: '4px ridge', borderColor: '#4878B6', 
		color: '#4878B6', margin: '1em auto', padding: '0 0 0 0', fontSize: '75%' }, 
		sTip: 'Add a Block to the Form', eventFuncs: [['click', butToolClick ],
		['selectstart', noTextSelect ],['mousedown', noTextSelect ]], 
		value: '____<br>____' } );
	panFormTool.add(butBlockTool);
		

//	if (bOldIE)
//		sValue = '<span style="height: 20px; width: 44px; color: #4878B6;"> | | </span>';
//	else
//		sValue = '<div style="display: inline-block; height: 20px; margin-left: 0px; ' +
//			   'border-right: 1px solid #4878B6;"></div>' +
//			   '<div style="display: inline-block; height: 20px; margin-left: 13px; ' +
//			   'border-right: 1px solid #4878B6;"></div>';

	var butLineTool = new Button( { id: 'fclinetool', classname: 'fcTool',  
		style: {height: '20px', width: '50px', border: '4px ridge', borderColor: '#4878B6', 
		color: '#4878B6', margin: '1em auto', padding: '0px' }, 
		sTip: 'Add a Line to the Form', eventFuncs: [['click', butToolClick ], 
		['selectstart', noTextSelect ],['mousedown', noTextSelect ]],
		value: ' | &nbsp;&nbsp; | ' } );
	panFormTool.add(butLineTool);

	var butFieldTool = new Button( { id: 'fcfieldtool', classname: 'fcTool',   
		style: {height: '14px', width: '48px', border: '2px solid', borderColor: '#3E4D60', fontSize: '0.6em',
		color: '#A3A3A3', backgroundColor: '#EFEFFF', margin: '1em auto', padding: '0px' }, 
		sTip: 'Add a data display / entry Field to the Form', eventFuncs: [['click', butToolClick ], 
		['selectstart', noTextSelect ],['mousedown', noTextSelect ]], value: 'Abc123' } );
	panFormTool.add(butFieldTool);

	var butButtonTool = new Button( { id: 'fcbuttontool', classname: 'fcTool', 
		style: {  height: '18px', width: '48px', margin: '1em auto', padding: '0px', 'border-color': '#4878B6', 
		'background-color': '#D0D0D0', color: '#355987' }, 
		sTip: 'Add a Button to the Form', eventFuncs: [['click', butToolClick ], 
		['selectstart', noTextSelect ],['mousedown', noTextSelect ]], value: 'Button' } );
	panFormTool.add(butButtonTool);

	var butCheckboxTool = new Button( { id: 'fccheckboxtool', classname: 'fcTool', 
		style: {height: '18px', width: '18px', margin: '1em auto', padding: '0px', 'border-color': '#4878B6', 
		'background-color': '#EFEFFF', color: '#355987', border: '2px solid', 'font-weight': 'bold' }, 
		sTip: 'Add a Checkbox to the Form', eventFuncs: [['click', butToolClick ], 
		['selectstart', noTextSelect ],['mousedown', noTextSelect ]], value: 'X' } );
	panFormTool.add(butCheckboxTool);


	var butRadioTool = new Button( { id: 'fcradiotool', classname: 'fcTool', 
		style: { height: '17px', width: '15px', margin: '1em auto', 'padding': '0px', 
		'background-color': '#EFEFFF', color: '#355987', border: '5px solid',
		'border-top-color': '#728BAC', 'border-right-color': '#C3CDDB', 'border-bottom-color': '#C3CDDB', 'border-left-color': '#728BAC',
		'font-weight': 'bold' }, 
		sTip: 'Add a Radio Button to the Form', eventFuncs: [['click', butToolClick ], 
			  ['selectstart', noTextSelect ],['mousedown', noTextSelect ]], value: '0' } );
	panFormTool.add(butRadioTool);

	butTableTool = new Button( { id: 'fctabletool', classname: 'fcTool',  
		style: {height: '42px', width: '32px', border: '2px solid', borderColor: '#4878B6', 
		color: '#4878B6', margin: '1em auto', padding: '0 0 0 0', fontSize: '50%' }, 
		sTip: 'Add a Table to the Form', eventFuncs: [['click', butTableToolClick ],
		['selectstart', noTextSelect ],['mousedown', noTextSelect ]], 
		value: '<table><tr><td></td><td></td><td></td></tr>' + 
			   '<tr><td></td><td></td><td></td></tr>' +
			   '<tr><td></td><td></td><td></td></tr></table>' } );
	panFormTool.add(butTableTool);

	var butTextTool = new Button( { id: 'fctexttool', classname: 'fcTool',   
		style: {height: '20px', width: '48px', border: '1px solid', borderColor: '#BBC5D3', 
		fontSize: '1.2em', fontStyle: 'italic', fontWeight: 'bold', fontFamily: 'Times New Roman', 
		color: '#618BC1', backgroundColor: '#E5E5EB', margin: '1em auto', padding: '0px' }, 
		sTip: 'Add Text to the Form', eventFuncs: [['click', butToolClick ], 
		['selectstart', noTextSelect ],['mousedown', noTextSelect ]], value: 'Text' } );
	panFormTool.add(butTextTool);

	// Switch the rootForm property to the Formset root form
	//blkFormCreator.rootForm = false;

	closeStyleTools();
	panBody.setEnabled(false);

	// Return the global blkFormCreator, but see comments at top of func
	return blkFormCreator;
}


function showFormCreator()
{
	var pageFormCreator = document.getElementById('pageformcreator');
	while (pageFormCreator.firstChild)
		pageFormCreator.removeChild(pageFormCreator.firstChild);
	var fc = new FormCreator(pageFormCreator);
}


function noTextSelect(ev)
{
	return false;
}


function formClick(ev)
{
	// Calls toggleButtonState() which will cater for toggle buttons and group 
	// buttons. A basic button which is neither will do nothing except a click 
	// movement which is caused by the CSS selector '.frmbutton:active'.
	ev = getEventProps(ev, blkFormCreator.elem);
	ev.stopPropagation();
	if (ev.target != ev.currentTarget)
		return;
	if (ev.target.srcForm.toggle)
		toggleButtonState(ev.target.srcForm);
//	return false;
}


function queryRootClick(ev)
{
	// Calls queryDatasource() on the rootForm.
	ev = getEventProps(ev);
	ev.stopPropagation();
	var	form = ev.target.srcForm;

	form.rootForm.clearData();

	form.rootForm.queryDatasource();
}


function queryFormClick(ev)
{
	// Calls queryDatasource() on the nearest ancestor form with a datasource that
	// has a non-null query. If no ancestor has a datasource, calls queryDatasource() 
	// on the rootForm.
	ev = getEventProps(ev);
	ev.stopPropagation();
	var	form = ev.target.srcForm;

//TODO	while ()

	//blkTfRootForm.queryDatasource();
}


function fcHover(ev)
{
	ev = getEventProps(ev, blkFormCreator.elem);
	if (ev.target != ev.currentTarget)
		return;
	if (ev.target.srcForm.hoverFunc)
		ev.target.srcForm.hoverFunc(ev);
	else if (ev.target.srcForm.hoverStyle)
	{
		var style = ev.target.srcForm.hoverStyle;
		var save = ev.target.srcForm.hoverSave;
		if (ev.type == 'mouseover')
		{
			if (! save)
			{
				ev.target.srcForm.hoverSave = {};
				save = ev.target.srcForm.hoverSave;
			}
			for (var prop in style)
			{
				var sDomProp = domPropSyntax(prop);
				save [sDomProp] = ev.target.style [sDomProp];
				ev.target.style [sDomProp] = style [prop];
			}
		}
		else if (save)
		{
			for (var prop in save)
				ev.target.style [prop] = save [prop];
		}
	}
	ev.stopPropagation();
}


function butPointerClick(ev)
{
	// Set cursor to tool shape
	ev = getEventProps(ev);
	panBody.elem.style.cursor = 'crosshair';
	selectedToolId = ev.target.id;
//>>>set bPointerActive
	closeStyleTools();
	ev.target.style.border = '1px solid #000000';
	ev.target.style.backgroundColor = '#CAD9E8';
	ev.stopPropagation();
}


function butToolClick(ev)
{
	// Set cursor to tool shape
	ev = getEventProps(ev);
	panBody.elem.style.cursor = 'crosshair';
	selectedToolId = ev.target.id;
	closeStyleTools();
	ev.stopPropagation();
}


function butTableToolClick(ev)
{
	// Cater for IE which can't do currentTarget
	ev = getEventProps(ev, butTableTool.elem);
	panBody.elem.style.cursor = 'crosshair';
	selectedToolId = ev.currentTarget.id;
	closeStyleTools();
	ev.stopPropagation();
}


function panMainMouseout(ev)
{
	ev = getEventProps(ev, blkFormCreator.elem);
	if (ev.target != ev.currentTarget)
		return;
	// Set cursor back to pointer shape
	panBody.elem.style.cursor = 'default';
	if (selectedToolId == 'fcpointer')
	{
		butPointer.elem.style.border = 'none';
		butPointer.elem.style.backgroundColor = 'transparent';	
	}
	selectedToolId = null;
	ev.stopPropagation();
}


function panRootClick(ev)
{
	var form, targetForm;
	
	ev = getEventProps(ev);
	// Set cursor back to pointer shape
	panBody.elem.style.cursor = 'default';

	if (! selectedToolId || ! ev.target)
		return;

	targetForm = getElementForm(ev.target);

	if (! targetForm)
		return;


	if (selectedToolId == 'fcpointer')
	{
		openStyleTools(targetForm);
		// Grey every other form in rootElem except styleTargetForm's ancestor forms
		greyRoundTargetForm(styleTargetForm, true);
		outlineForm(styleTargetForm, true);
	}
	else
	{
		// Add selected Form to target form
		if (selectedToolId == 'fcpaneltool')
			form = new Block( { fitwidth: true, style: { width: '5em', height: '5em', margin: '0.3em' } } );
		else if (selectedToolId == 'fclinetool')
			form = new Line({ fitwidth: true, style: { height: '1.2em', margin: '0.3em' } });
		else if (selectedToolId == 'fcfieldtool')
			form = new Field({ fitwidth: true, style: { height: '1.3em', margin: '0.1em' } });
		else if (selectedToolId == 'fcbuttontool')
			form = new Button( { eventFuncs: [['click', formClick ],['selectstart', noTextSelect ]] } );
		else if (selectedToolId == 'fccheckboxtool')
			form = new Button( { toggle: true, value: 'X', classname: 'frmcheckbox',
				eventFuncs: [['click', formClick ], ['selectstart', noTextSelect ],
				['mousedown', noTextSelect ]] } );
		else if (selectedToolId == 'fcradiotool')
			form = new Button( { toggle: true, buttonGroupForm: targetForm, value: 'O',
				classname: 'frmradio', eventFuncs: [['click', formClick ], 
				['selectstart', noTextSelect ],['mousedown', noTextSelect ]] } );
		else if (selectedToolId == 'fctabletool')
	//var table = new Table( 
	//	{
	//	style: { width: '10em' },
	//	caption: { datasource: { data: 'Table Caption' } },
	//	thead: { datasource: { data: ['Col 1 heading','Col 2 heading'] } }, 
	//	tbody:	{ datasource: { data: [  ['Col 1 data','Col 2 data' ] ] } }
	//	}
			form = new Table(
				{	style: { width: 'auto', overflow: 'auto', border: '1px solid' },
					caption: {  },
					thead: {  }, 
					tbody:	{  }
					,


					datasource:
					{
						id: '',
						data:
						{
							colid: [ '','' ],
							caption: { datasource: { data: '' } },
							thead: { datasource: { data: ['',''] } },
							tbody: { datasource: { data: [  ['','' ] ] } }
						},
						datatype: DS_DTATYP_TABLE
					}
				} );
		else if (selectedToolId == 'fctexttool')
			form = new Text();
		else if (selectedToolId == 'fcselecttool')
			form = new Select();
		else
		{
			ev.stopPropagation();
			return;
		}

		targetForm.add(form);
		
		// Bind some sample data for style display/edit purposes
		if (selectedToolId == 'fcfieldtool')
			form.bindData( { data: 'AbCd12', datatype: DS_DTATYP_VALUE } );

		bFormModified = true;
	}

	butSaveForm.setEnabled(true);
	selectedToolId = null;
	ev.stopPropagation();
}

//var saveStylePos, saveStyleOflow;
var saveStylePos;


function outlineForm(form, highlightOn)
{
// TODO >>> Use the CSS outline property: "outline: 3px solid #FFFF33;" if IE8
	// is detected. All other browsers except IE 4-7 support it.

	// Outline form edges.
	var borderTop, borderRight, borderBottom, borderLeft;

//	if (! form || ! form.elem.offsetParent)
	if (! form)
		return;
	if (! highlightOn)
	{
		elemStore.appendChild(outlineElem);
form.elem.parentNode.style.position = saveStylePos;
//		form.elem.style.position = saveStylePos;
//		form.elem.style.overflow = saveStyleOflow;
		return;
	}

	borderTop = getComputedStylePx(form.elem, 'border-top-width');
	borderRight = getComputedStylePx(form.elem, 'border-right-width');
	borderBottom = getComputedStylePx(form.elem, 'border-bottom-width');
	borderLeft = getComputedStylePx(form.elem, 'border-left-width');
	//saveStylePos = form.elem.style.position;
	//form.elem.style.position = 'relative';
	//saveStyleOflow = form.elem.style.overflow;
	//form.elem.style.overflow = 'visible';

saveStylePos = form.elem.parentNode.style.position;
form.elem.parentNode.style.position = 'relative';

	try
	{
//		outlineElem.style.top = -(borderTop + 1) + 'px';
//		outlineElem.style.left = -(borderLeft + 1) + 'px';
		// Why is it necessary to subtract 1 in the next line?
		outlineElem.style.top = ((form.elem.offsetTop - outlineBorderWidth) - 1) + 'px';
		outlineElem.style.left = (form.elem.offsetLeft - outlineBorderWidth) + 'px';
		outlineElem.style.width = (form.elem.offsetWidth) + 'px';
		outlineElem.style.height = (form.elem.offsetHeight) + 'px';
	}
	catch(e)
	{
		outlineElem = null;
		return;
	}

//	form.elem.appendChild(outlineElem);
	form.elem.parentNode.appendChild(outlineElem);
}


function greyRoundTargetForm(target, bOnOff)
{
	// Grey, or ungrey, all child forms in parent, and all ancestor forms.
	// By skipping the initial target form and all subsequent ancestor forms,
	// we avoid greying the initial target by greying an ancestor.

	if (! target.parent)
		// Root form reached
		return;

	var parent = target.parent;

	for (child in parent.childForms)
	{
		var form = parent.childForms [child];
		if (form != target)
			if (bOnOff)
				form.elem.style.opacity = '0.4';
			else
				form.elem.style.opacity = '';
	}

	greyRoundTargetForm(parent, bOnOff);
}

// *UpdateStyle* Changing a Form's dimensions.
// ------------------------------
// Any time a form's font (and therefore form.fontsizepx), width, padding, border or margin 
// change, the form's iTotWidthIncMargEm must be recalculated, and the forms's 
// parent.childWidthEm must be checked. If the form's iTotWidthIncMargEm has increased, 
// and the previous value equalled the parent.childWidthEm, then parent.childWidthEm 
// must be set to the new iTotWidthIncMargEm. Otherwise, whether iTotWidthIncMargEm 
// increased or decreased, the parent.childWidthEm must be rechecked against all 
// the parent's child forms. 



//---------- Styles and Stylesheets ------------------------------
//	document.styleSheets [].cssRules []

// There are 3 'APIs' for manipulating stylesheets dynamically in the DOM:
//
// 1) The W3C DOM Level 2 CSSStyleSheet Spec way, through the styleSheet object.
//
// 2) The current browser (IE7/FF3) 'cross-browser' way, through the <head>.<style> element.
// 
// 3) The IE-only createStyleSheet() method + the 'cross-browser' way.
//
// This section currently implements No 2), with Nos 1) and 3) in commented code, 
// "W3C when ready" and "IE (if needed)".


function FrmStyleSheet(id)
{
	// Forms FrmStyleSheet object
	// Structure:
	//
	//	FrmStyleSheet {
	//		cssRules [ 
	//			FrmCSSStyleRule { 
	//				selectorText: '#myelem'
	//				style { 
	//					color: 'red' 
	//					width: '5em' 
	//				}
	//			}
	//			.....
	//		]
	// }
	//
	// W3C CSS DOM equivalent:
	//
	//	StyleSheet {
	//		cssRules [ 
	//			CSSStyleRule { 
	//				selectorText: '#myelem'
	//				style { 
	//					color: 'red' 
	//					width: '5em' 
	//				}
	//			}
	//			.....
	//		]
	// }

	this.id = id; 

	if (bOldIE)
	{
		this.styleSheet = document.createStyleSheet();
//>>>NO. Get styleElem from styleSheet, then set id
		this.styleElem = this.styleSheet.owningElement || styleSheet.ownerNode;
		this.bSheetAddedToDoc = true;
	}
	else
	{
		this.styleElem = document.createElement("style");
		this.styleElem.type = "text/css";
		this.styleElem.media = "screen"; 
		this.styleSheet = null;
		this.bSheetAddedToDoc = false;
	}
	this.styleElem.id = this.id; 


	// W3C when ready - DOM Level 2 CSS 2.2.2. Style sheet creation
	//this.styleSheet = createCSSStyleSheet(title, media);

	this.cssRules = [];


	this.insertRule = fcInsertStyleRule;
	this.deleteRule = fcDeleteStyleRule;
	this.getRule = fcGetStyleRule;
	this.getRuleDomIndex = fcGetStyleRuleDomIndex;
	this.getRulePropertyValue = fcGetRulePropertyValue;
	this.getCssText = fcGetStyleSheetCssText;
	this.addStyleSheetToDoc = fcAddStyleSheetToDoc;
	this.deleteStyleSheet = fcDeleteStyleSheet;
	this.mergeStylesheet = fcMergeStylesheet;
}



function fcAddStyleSheetToDoc()
{
	// Add the Forms FrmStyleSheet into the DOM document

	if (bOldIE)
	{
		// Empty StyleSheet was automatically added by IE when created in FrmStyleSheet()
		this.styleSheet.cssText = this.getCssText();
	}
	else
	{
		//this.styleElem.innerHTML = this.getCssText();

		// Ref: http://yuiblog.com/blog/2007/06/07/style/
		if (this.styleElem.styleSheet)
			// IE
			this.styleElem.styleSheet.cssText = this.getCssText();
		else
			this.styleElem.appendChild(document.createTextNode(this.getCssText()));
		var elemHead = document.getElementsByTagName('head')[0];
		elemHead.appendChild(this.styleElem);
		if (this.styleElem.styleSheet)
			// IE
			this.styleSheet = this.styleElem.styleSheet;
		else
			this.styleSheet = this.styleElem.sheet;

		// TODO Why did we have a styleSheet.id property? IE won't allow next line
		//this.styleSheet.id = this.id;

		this.bSheetAddedToDoc = true;
	}
	// This was the 1st method of setting this.styleSheet. Keep in case IE doesn't allow
	// direct assignment as in above line.
	//for (var prop in document.styleSheets)
	//{
	//	var sheet = document.styleSheets [prop];
	//
	//	if (sheet.ownerNode.id == this.id)
	//	{
	//		this.styleSheet = sheet;
	//		this.styleSheet.id = this.id;
	//		break;
	//	}
	//}

	// W3C when ready - DOM Level 2 CSS 2.2.2. Style sheet creation
	// NB: but you can't add it to the document!!!
	// "This interface allows the DOM user to create a CSSStyleSheet outside the context of a document. 
	//		There is no way to associate the new CSSStyleSheet with a document in DOM Level 2."

// TODO>>> This is just for the benefit of fcGetStylePropertyFromStore().
// Otherwise the stylesheets variable is not used. Do we really need it?
	stylesheets [this.id] = this;
}


function fcDeleteStyleSheet()
{
	for (var i = 0; i < document.styleSheets.length; i ++)
	{
		if (document.styleSheets [i].id == this.sTitle)
		{
			document.styleSheets.removeChild(document.styleSheets [i]);
		}
	}

}


function fcInsertStyleRule(styleRule)
{
	// Add the rule to this FrmStyleSheet
	this.cssRules.push(styleRule);
	styleRule.sheet = this;
	// If the selector string  is actually a comma-separated selector group, 
	// store the individual selectors, and their index in cssRules, in the
	// global selectorGroups so that other functions, e.g. fcGetStylePropertyFromStore,
	// can locate the individual selector rapidly.
	//
	// E.G:		styleRule:				'.classa, .classb { margin: 5px; }'
	//			styleRule.selectorText:	'.classa, .classb'
	//			selectorGroups:			{ ... 
	//									'.classa': '.classa, .classb',
	//									'.classb': '.classa, .classb', 
	//									... }
	if (styleRule.selectorText.indexOf(',') != -1)
	{
		var aSels = styleRule.selectorText.split(/[, ]+/);
		for (var s = 0; s < aSels.length; s ++)
			selectorGroups [aSels [s]] = this.cssRules.length - 1;
	}

	// If the Forms Style Sheet has already been inserted into the DOM, then we need
	// to see the effect immediately. Add the rule to the DOM Style Sheet.
	if (this.bSheetAddedToDoc)
	{
		if (this.styleSheet)
		{
			if (this.styleSheet.insertRule)
			{
				// W3C
				this.styleSheet.insertRule(styleRule.getCssText(), this.styleSheet.cssRules.length); 
			}
			else if (this.styleSheet.addRule)
			{
				// IE
				// addRule() won't accept selector groups, i.e.  comma-separated lists of selectors

				saSelector = styleRule.selectorText.split(',');
				for (var i = 0; i < saSelector.length; i ++)
					this.styleSheet.addRule(saSelector [i], styleRule.getDeclBlockText()); 
			}
			else
			{
				// Append the cssText of styleRule to the <head>.<style> element
				this.styleElem.innerHTML += styleRule.getCssText();
			}
			styleRule.bRuleInsertedInDom = true;
		}
	}
}


function fcDeleteStyleRule(selectorText)
{
	var iIndex = this.getRuleDomIndex(selectorText);
	if (iIndex != -1)
	{
		if (this.styleSheet.deleteRule)
			// W3C
			this.styleSheet.deleteRule(iIndex); 
		else if (this.styleSheet.removeRule)
			// IE
			this.styleSheet.removeRule(iIndex); 
	}
	if (selectorText.indexOf(',') != -1)
	{
		// See comments re 'selector group' in fcInsertStyleRule above
		var aSels = selectorText.split(/[, ]+/);
		for (var s = 0; s < aSels.length; s ++)
			delete selectorGroups [aSels [s]];
	}
}


function fcMergeStylesheet(sheet)
{
	for (var i = 0; i < sheet.cssRules.length; i ++)
		this.insertRule(sheet.cssRules [i]);
}


function fcGetStyleRule(selectorText)
{
	for (var i = 0; i < this.cssRules.length; i ++)
	{
		if (this.cssRules [i].selectorText == selectorText)
			return this.cssRules [i];
	}
	return null;
}


function fcGetStyleRuleDomIndex(selectorText)
{
	var iIndex = 0, cssRules;

	if (this.styleSheet && this.styleSheet.cssRules)
		// W3C
		cssRules = this.styleSheet.cssRules;
	else if (this.styleSheet && this.styleSheet.rules)
		// IE
		cssRules = this.styleSheet.rules;
		
	for (var iIndex = 0; iIndex < cssRules.length; iIndex ++)
	{
		if (this.styleSheet.cssRules [iIndex].selectorText == selectorText)
			return iIndex;
	}
	return -1;
}


function fcGetRulePropertyValue(selectorText, property)
{
	var value, cssRules;

	if (this.styleSheet && this.styleSheet.cssRules)
		// W3C
		cssRules = this.styleSheet.cssRules;
	else if (this.styleSheet && this.styleSheet.rules)
		// IE
		cssRules = this.styleSheet.rules;
		
	if (cssRules)
	{
		for (var i = 0; i < cssRules.length; i ++)
		{
			if (cssRules [i].selectorText == selectorText)
			{
				value = cssRules [i].style [property];
			}
		}
	}
	else
	{
		// form.js
		for (var i = 0; i < this.cssRules.length; i ++)
		{
			if (this.cssRules [i].selectorText == selectorText)
			{
				value = this.cssRules [i].oDeclaration [property];
			}
		}
	}

	if (value && value.length > 9 && value.substring(0,4) == 'rgb(')
	{
		var a = value.match(/rgb\(([0-9,]+)\)/);
		if (a)
			value = a [1];
	}
	return value;
}


function fcGetStyleSheetCssText()
{
	// Concatenate all the text in all the rules in this.cssRules
	var cssText = '';

	for (var i = 0; i < this.cssRules.length; i ++)
	{
		cssText += this.cssRules [i].getCssText();
	}
	return cssText;
}


function FrmCSSStyleRule(selectorText, oDeclarationBlock)
{
	// Forms FrmCSSStyleRule object
	//
	// selectorText is a classname ('.myclass') or element ID ('#myid') or element type ('h1').
	//
	// oDeclarationBlock is an object whose properties are CSS declarations with CSS property names 
	// and CSS property values.
	//
	// E.g. myRule = new FrmCSSStyleRule('.myfield', { display: 'inline', border-top: '1px solid' } );
	//
	// OR
	//
	// myRule = new FrmCSSStyleRule('.myfield');
	// myRule.setDeclaration('display', 'inline');
	// myRule.setDeclaration('border', '1px solid');
	//
	// The DOM path for the CSS rule, R, for a particular stylesheet, S:
	//		document.styleSheets [S].cssRules [R]
	//
	// The FrmCSSStyleRule.getCssText() method returns the actual CSS rule (...cssRules [R].cssText):
	//		".inline { display: inline; border: 1px solid; margin: 1em; padding: 0.5em; }"
	//
	// The arg oDeclarationBlock is optional. If it is null it will be created empty.

	if (! selectorText)
		return null;

	// Parent FrmStyleSheet
	this.sheet;

	// The selector part of the rule
	this.selectorText = selectorText;

	// The declaration part of the rule
	if (oDeclarationBlock)
		this.style = oDeclarationBlock;
	else
		this.style = {};

	this.bRuleInsertedInDom = false;

	this.getCssText = fcGetStyleRuleCssText;
	this.getDeclBlockText = fcGetStyleRuleDeclBlockText;
	//this.setDeclaration = fcSetDeclaration; (Use this.setProperty instead)
	this.deleteDeclaration = fcDeleteDeclaration;
	this.parseDeclaration = fcParseDeclaration;
	// W3C DOM Level 2 CSS CSSStyleDeclaration interface methods
	//
	// These functions can be replaced by direct assignment if the browser supports
	// the CSS2Properties Interface as an object within the CSSStyleDeclaration.
	//
	// The browser supplies a CSS2Properties object which is exactly like the 
	// style object of the DOM element. It is an interface definition of a style 
	// object whose properties are every CSS property, in camelCase, with the 
	// value being the CSS declaration.
	// 
	// So:
	//		document.styleSheets [n].cssRules [n].style.setProperty('color', 'red');
	// can be replaced by:
	//		document.styleSheets [n].cssRules [n].style ['color'] = 'red';
	// See:
	// http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration
	// http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSS2Properties
	this.getPropertyValue = fcGetPropertyValue;
	this.getPropertyCSSValue = fcGetPropertyCSSValue;
	this.removeProperty = fcRemoveProperty;
	this.setProperty = fcSetProperty;
}


function fcGetStyleRuleCssText()
{
	var sText = this.selectorText + ' { ' + this.getDeclBlockText() + ' }';
	return sText
}


function fcGetStyleRuleDeclBlockText()
{
	// 
	var sDeclBlock = '';

	for (var prop in this.style)
	{
		var sDecl = cssPropSyntax(prop) + ': ' + this.style [prop] + ';';
		if (sDeclBlock.length)
			sDeclBlock += ' ';
		sDeclBlock += sDecl;
	}
	return sDeclBlock;
}


//function fcSetDeclaration(sCssPropName, sCssPropValue)
//{
//	// Add a single CSS declaration into FrmCSSStyleRule.style
//	this.style [domPropSyntax(sCssPropName)] = sCssPropValue;
//}


function fcDeleteDeclaration(sCssPropName)
{
	delete this.style [domPropSyntax(sCssPropName)];
}


function fcParseDeclaration(oDestDecl, sPropPrefix)
{
	// For every declaration in this FrmCSSStyleRule's style whose property name
	// partially or wholly matches 'sPropPrefix', put that declaration into oDestDecl. If a
	// declaration with that property name already exists in oDestDecl, it will be 
	// overwritten by the later occurring declaration, in conformance with the CSS cascade.
	// If sPropPrefix is null, get all properties.
	for (var propname in this.style)
	{
		if (! sPropPrefix || propname.substr(0, sPropPrefix.length) == sPropPrefix)
		{
			var sPropVal = this.style [propname];
			parseCssDeclaration(propname, sPropVal, oDestDecl)
		}
	}
}


function fcGetPropertyValue(sCssPropName)
{
}


function fcGetPropertyCSSValue(sCssPropName)
{
}


function fcRemoveProperty(sCssPropName)
{
}


function fcSetProperty(sCssPropName, sCssPropValue)
{
	// Add a single CSS declaration into FrmCSSStyleRule.style
	this.style [domPropSyntax(sCssPropName)] = sCssPropValue;

	// If the Forms Style Sheet has already been inserted into the DOM, then we 
	// need to see the effect immediately. Set the declaration into the DOM 
	// styleSheet rule.
	if (this.sheet && this.sheet.bSheetAddedToDoc)
	{
		var cssRules;

		if (this.sheet.styleSheet && this.sheet.styleSheet.cssRules)
			// W3C
			cssRules = this.sheet.styleSheet.cssRules;
		else if (this.sheet.styleSheet && this.sheet.styleSheet.rules)
			// IE
			cssRules = this.sheet.styleSheet.rules;
			
		if (cssRules)
		{
			var iLastRule = -1;
			for (var i = 0; i < cssRules.length; i ++)
			{
				if (cssRules [i].selectorText == this.selectorText)
					iLastRule = i;
			}
			// iLastRule cant be -1 if this.sheet.bSheetAddedToDoc is true (?)
			if (iLastRule != -1)
				cssRules [iLastRule].style [sCssPropName] = sCssPropValue;
		}
	}
}


function fcCreateStyleSheet(id, cssRules)
{
	var sheet = null;

	if (id)
		sheet = new FrmStyleSheet(id);
	if (sheet && cssRules && (cssRules instanceof Array))
	{
		for (var r = 0; r < cssRules.length; r ++)
		{
			var rule = new FrmCSSStyleRule(cssRules [r].selectorText, cssRules [r].style);
			sheet.insertRule(rule);
		}
	}
	return sheet;
}


function fcGetStylePropertyFromStore(sProperty, selectorText, sSheetId, bParse)
{
	// Get the value of the Style Property 'sProperty' from the Stylesheet store.

//TODO>>> list must have separators that allow for nulti-token selectors, e.g. '#myelem input'
//This whole function must be done away with. It is mostly used redundantly by Form.setStyle.
//It has been/will be replaced by FrmStyleSheet functions.
	// If selectorText is set, it may be a list of one or more selectors. Look only in those selectors.

	// If sSheetId is set, it may be a list of one or more stylesheets. Look only in those stylesheets.
	//
	// If bParse is true return a declaration block with all 'longhand' properties matching 'sProperty',
	// else return a string with the property value of exactly 'sProperty'; if sProperty is
	// a 'shorthand' property this string may contain multiple values.
	//
	// E.g: if the store yields { margin: 1em 3em 2em 4em, margin-left: 5em } and sProperty = 'margin' 
	// and bParse = false, then return the propval string '1em 3em 2em 4em'; If bParse is true, return
	// the oDeclarationBlock object { margin-top: 1em; margin-right: 3em; margin-bottom: 2em; margin-left 5em; }.
	// NOTE: This is also wrong. 'margin' should return the cascaded shorthand property value:
	// '1em 3em 2em 5em'. (DOM Level 2 CSS para 2.3)
	// 
	// If a property is encountered more than once, either in a declaration or a stylesheet or in multiple
	// sheets in the store, the last property is the one returned, in conformance with the CSS cascade.
	var propval;
	var oDeclarationBlock;
	var saSheetId = [];

	// Force CSS property syntax (e.g. 'margin-top' not 'marginTop')
	sProperty = cssPropSyntax(sProperty);

	if (set(sSheetId))
		saSheetId = sSheetId.split(' ');
	else
	{
		for (var ssidprop in stylesheets)
			saSheetId.push(ssidprop);
	}

	for (var iSheet = 0; iSheet < saSheetId.length; iSheet ++)
	{
		var sheet, sheetid, saSelector = [], iRule = 0;

		sheet = stylesheets [saSheetId [iSheet]];
		if (! set(sheet))
		{
			if (set(sSheetId) && iSheet < saSheetId.length)
				continue;
			else
				break;
		}

		if (set(selectorText))
			saSelector = selectorText.split(' ');
		else
		{
			for (var i = 0; i < sheet.cssRules.length; i ++)
				saSelector.push(sheet.cssRules [i].selectorText);
		}

		for (var iSel = 0; iSel < saSelector.length; iSel ++)
		{
			var rule;

			for (var i = 0; i < sheet.cssRules.length; i ++)
			{
				if (sheet.cssRules [i].selectorText == saSelector [iSel])
					rule = sheet.cssRules [i];
			}

			// If the selector was one of a selector group, it won't have been found.
			// (A regex could be used to locate the saSelector [iSel] token but would be slow)
			// Look in the global selectorGroups for the individual selector and 
			// its index in cssRules.
			// E.G:		Selector: '.classb'. 
			//			Rule: '.classa, .classb { margin: 5px; }'
			//			selectorGroups property: { ... '.classb': 7 ... }
			if (! rule)
			{
				var i = selectorGroups [saSelector [iSel]];
				if (set(i))
					rule = sheet.cssRules [i];
			}
			if (! rule)
			{
				if (set(selectorText) && iSel < saSelector.length)
					continue;
				else
					break;
			}

			if (bParse || sProperty == null)
			{
				if (! oDeclarationBlock)
					oDeclarationBlock = {};

				// If sProperty is null, get all properties
				rule.parseDeclaration(oDeclarationBlock, sProperty);
			}
			else
			{
				var declval = rule.style [sProperty];
				if (set(declval))
					propval = declval;
			}
		}
	}

	// Return multiple properties, a single property or null
	if (oDeclarationBlock)
		return oDeclarationBlock;
	else
		return propval;
}


function parseCssDeclaration(sPropName, sPropVal, oDestDecl)
{
// Can this cope with: (especially the rgb() syntax)?
//border "4px ridge rgb(128, 128, 128)"
//border-left "4px ridge rgb(128, 128, 128)"
//border-left-color "rgb(128, 128, 128)"
//border-left-style "ridge"
//border-left-width "4px"

	// Create and return a CSS declaration object, with one or more properties, from 
	// the passed property name and value. If an existing declaration is passed in 
	// oDestDecl, add the property or properties to oDestDecl and return oDestDecl, 
	// else return a new declaration.
	//
	// Only 'longhand' properties are set into oDeclarationBlock; i.e. if sPropName is 'margin',
	// it is a 'shorthand' property that actually represents four 'longhand' properties: 
	// margin-top, margin-right, margin-bottom and margin-left. The value sPropVal is 
	// parsed into its four component declarations which are set in oDeclarationBlock.
	var oDeclarationBlock;
	var saPropVals = [];
	var T, L, B, R;

	// Split sPropVal into space-separated tokens, then reassemble any
	// sequence of tokens that form a url(...) or rgb(...) value.
	var sa = String(sPropVal).split(/\s+/);
	var bSeq = false, sTok;
	for (var i = 0; i < sa.length; i ++)
	{
		var bAdded = false;
		if (sa [i].indexOf('(') != -1)
		{
			bSeq = true;
			sTok = sa [i];
			bAdded = true;
		}
		if (sa [i].indexOf(')') != -1)
		{
			if (! bAdded)
			{
				sTok += sa [i];
				bAdded = true;
			}
			saPropVals.push(sTok);
			bSeq = false;
		}
		if (! bAdded)
		{
			if (bSeq)
				sTok += sa [i];
			else
				saPropVals.push(sa [i]);
		}
	}


	if (oDestDecl)
		oDeclarationBlock = oDestDecl;
	else
		oDeclarationBlock = {};

	// If sPropName is DOM-syntax, force it into CSS-syntax
	sPropName = cssPropSyntax(sPropName);

	switch (sPropName)
	{
		case 'background':
			// CSS 14.2.1 provides for the values to be in any order. This requires some
			// heavy parsing, e.g. url(...), red, repeat, #FF0080, left, fixed.... 
			// Items 2 and 4 are colours. So for now use 'background-color'.
			break;
		case 'border':
			if (saPropVals.length > 0)
				oDeclarationBlock ['border-top-width'] = oDeclarationBlock ['border-right-width'] = 
				oDeclarationBlock ['border-bottom-width'] = oDeclarationBlock ['border-left-width'] = 
				saPropVals [0];
			if (saPropVals.length > 1)
				oDeclarationBlock ['border-top-style'] = oDeclarationBlock ['border-right-style'] = 
				oDeclarationBlock ['border-bottom-style'] = oDeclarationBlock ['border-left-style'] = 
				saPropVals [1];
			if (saPropVals.length > 2)
				oDeclarationBlock ['border-top-color'] = oDeclarationBlock ['border-right-color'] = 
				oDeclarationBlock ['border-bottom-color'] = oDeclarationBlock ['border-left-color'] = 
				saPropVals [2];
			break;
		case 'border-top':
		case 'border-right':
		case 'border-bottom':
		case 'border-left':
			if (saPropVals.length > 0)
				oDeclarationBlock [sPropName + '-width'] = saPropVals [0];
			if (saPropVals.length > 1)
				oDeclarationBlock [sPropName + '-style'] = saPropVals [0];
			if (saPropVals.length > 2)
				oDeclarationBlock [sPropName + '-color'] = saPropVals [0];
			break;
		case 'border-color':
		case 'border-style':
		case 'border-width':
			if (saPropVals.length == 1)
			{
				T = L = B = R = saPropVals [0];
			}
			else if (saPropVals.length == 2)
			{
				T = B = saPropVals [0];
				L = R = saPropVals [1];
			}
			else if (saPropVals.length == 3)
			{
				T = saPropVals [0];
				R = L = saPropVals [1];
				B = saPropVals [2];
			}
			else if (saPropVals.length == 4)
			{
				T = saPropVals [0];
				R = saPropVals [1];
				B = saPropVals [2];
				L = saPropVals [3];
			}
			var iCSW = sPropName.indexOf('-') + 1;
			var sCSW = sPropName.substring(iCSW);
			oDeclarationBlock ['border-top-' + sCSW] = T;
			oDeclarationBlock ['border-right-' + sCSW] = R;
			oDeclarationBlock ['border-bottom-' + sCSW] = B;
			oDeclarationBlock ['border-left-' + sCSW] = L;
			break;
		case 'font':
			break;
		case 'margin':
			if (saPropVals.length == 1)
			{
				T = L = B = R = saPropVals [0];
			}
			else if (saPropVals.length == 2)
			{
				T = B = saPropVals [0];
				L = R = saPropVals [1];
			}
			else if (saPropVals.length == 3)
			{
				T = saPropVals [0];
				R = L = saPropVals [1];
				B = saPropVals [2];
			}
			else if (saPropVals.length == 4)
			{
				T = saPropVals [0];
				R = saPropVals [1];
				B = saPropVals [2];
				L = saPropVals [3];
			}
			oDeclarationBlock ['margin-top'] = T;
			oDeclarationBlock ['margin-right'] = R;
			oDeclarationBlock ['margin-bottom'] = B;
			oDeclarationBlock ['margin-left'] = L;
			break;
		case 'padding':
			break;

		// Anything else must be a specific, single-value property
		default: oDeclarationBlock [sPropName] = saPropVals [0];
	}
	return oDeclarationBlock;
}


// **************************** Test Form ******************************************
//>>> also, set fldDatsrcId with target`s current id as done with background colour, etc

var dsTest1 =
		{
			data:
			{	tfpanroot: 'root panel', tfpantop: 'top panel', tfpanmid: 'middle panel', 
				tfpanbot: 'bottom panel', tfpanlef: 'left panel', tfpancen: 'centre panel', 
				tfpanrig: 'right panel'
			},
			datatype: DS_DTATYP_DTASRC
		};

var fldTfFormId;
var blkTfShowFrm;
var blkTfFrmCrtTab;
var blkTfShowFrmTab;
var blkTfRootForm;
var tfOpenedFormId;


function tfFrmCrtClick(ev)
{
	ev = getEventProps(ev);
	ev.stopPropagation();

	openTab(blkTfFrmCrtTab);
}


function tfShowFrmClick(ev)
{
	ev = getEventProps(ev);
	ev.stopPropagation();

	var id = fldTfFormId.elem.value;
	if (id == '')
		return false;
	tfOpenedFormId = null;

	if (blkTfRootForm)
		blkTfRootForm.deleteForm();

	openTab(blkTfShowFrmTab);

	// Load the Form from the formTemplates store, or the server, 
	// and display in the Form page.
	tfOpenedFormId = id;
	if (formTemplates [id])
		tfLoadOpenedForm();
	else
	{
		// Try server.
		// Set the callback for retLoadFormTplatesSvr
		retRetLoadFormTplatesSvr = tfLoadOpenedForm;
		loadFormTplatesSvr(id);
	}
}


function tfLoadOpenedForm(iStatus, sResult)
{
	if (set(iStatus) && iStatus != RPC_OK)
	{
		setStatus('Error opening Form: ' + sResult, C_ERROR);
		return;
	}
		
	if (! tfOpenedFormId)
		return;

	blkTfRootForm = loadFormFromTplateStore(tfOpenedFormId, blkTfShowFrmTab.elem);
	if (! blkTfRootForm)
	{
		setStatus('Form ' + tfOpenedFormId + ' not found. ' + sResult, C_ERROR);
		tfOpenedFormId = null;
		return;
	}
}


function tfSetDataClick(ev)
{
	ev = getEventProps(ev);
	ev.stopPropagation();

	if (! blkTfRootForm)
		return;

	blkTfRootForm.queryDatasource();

var x = 0;
}


function testForm()
{
	frmInit();

//var f = tfShowFrmClick;
//var t = typeof(f);
//f = Block;
//t = typeof(f);


	// The root form must be displayed before any other form in a formset is added.
	//
	// Form.add() will not work correctly, and cannot be called, until the root form
	// has been added to the DOM, and no child or ancestor of the root form has 
	// 'display: none'. The style 'visibility: hidden' is allowed, and may be used if 
	// the Form must not appear on the screen. If the initial state of any form must 
	// be 'display: none', for example to create a 'card' layout, it must be set when
	// all forms have been added.
	//
	// No child form can be added to its parent form unless the parent form is in the DOM,
	// i.e. has been added to its parent form.
	//
	// The reason for this is that the property 'offsetWidth' is used in some layout
	// calculations, and this property is zero until the browser actually renders the DOM.

	var pageForm = document.getElementById('pageform');
	while (pageForm.firstChild)
		pageForm.removeChild(pageForm.firstChild);
	showActionPanel('pageform');

	// Create Test Form
	var blkTfTest = new Block({ id: 'tfpantest' });//, rootForm: true  });
	//blkTfTest.rootForm = true;
	pageForm.appendChild(blkTfTest.elem);

	var linTfBut = new Line({ id: 'tfpanbut', style: { height: '2em', backgroundColor: '#CAE7C5' } });
	blkTfTest.add(linTfBut);
	var butTfFrmCrt = new Button( { value: 'Form Creator', eventFuncs: [['click', tfFrmCrtClick ]],
		style: { width: '6em' } } );
	linTfBut.add(butTfFrmCrt);
	var butTfShowFrm = new Button( { value: 'Show Form', eventFuncs: [['click', tfShowFrmClick ]] } );
	linTfBut.add(butTfShowFrm);
	fldTfFormId = new Field( { id: 'tfformid', label: 'Form Id:' } );
	linTfBut.add(fldTfFormId);
	var butTfSetData = new Button( { value: 'Set Data', eventFuncs: [['click', tfSetDataClick ]] } );
	linTfBut.add(butTfSetData);

	blkTfShowFrm = new Block();
	blkTfTest.add(blkTfShowFrm);

	blkTfFrmCrtTab = new Block();
	blkTfShowFrm.add(blkTfFrmCrtTab);
	blkTfFrmCrtTab.shut();

	blkTfShowFrmTab = new Block();
	blkTfShowFrm.add(blkTfShowFrmTab);
	blkTfShowFrmTab.shut();

	var fc = new FormCreator(blkTfFrmCrtTab.elem); // Creates blkFormCreator

	//var table = new Table( 
	//	{
	//	style: { width: '10em' },
	//	caption: { datasource: { data: 'Table Caption' } },
	//	thead: { datasource: { data: ['Col 1 heading','Col 2 heading'] } }, 
	//	tbody:	{ datasource: { data: [  ['Col 1 data','Col 2 data' ] ] } }
	//	}
	//	//{	id: 'tftable', 
	//	//	style: { width: '40em' },
	//	//	stylesheet: { id: 'sheet_testtd', cssRules: [ { selectorText: '.class_testtd td', 
	//	//		style: { 'white-space': 'normal', padding: '3px' } } ] },

	//	//	// Table has caption, heading and tbody which is set here in the constructor call.
	//	//	//
	//	//	//caption: { datasource: { data: 'Test table Caption' } },
	//	//	//thead: { datasource: { data: ['Col head one','Second col head','Third and last column heading'] } }, 
	//	//	//tbody:	{	classname: 'class_testtd', style: { backgroundColor: '#B3E5C9' }, 
	//	//	//			datasource:
	//	//	//			{ data:
	//	//	//				[ 
	//	//	//					['Col 1, Row 1','Col 2, Row 1','Col 3, Row 1'], 
	//	//	//					['Col 1, Row 2','Col 2, Row 2 aaa bbbbb cccc ddddd eeee ffff','Col 3, Row 2'], 
	//	//	//					['Col 1, Row 3','Col 2, Row 3','Col 3, Row 3']
	//	//	//				]
	//	//	//			}
	//	//	//		}

	//	//	// Table has caption, heading and tbody; datasource is external and is set 
	//	//	// after table creation by calling table.setTableDatasource() and table.bindTable().
	//	//	caption: {},
	//	//	thead: {}, 
	//	//	tbody:	{ classname: 'class_testtd', style: { backgroundColor: '#B3E5C9' } }
	//	//}
	//);
	//panRoot.add(table);
//	table.setTableDatasource(dsTestTd);
//	table.bindTable();


var x = 1;
}


function xtestForm()
{
}


/*
Author: Rob Reid
Create Date: 08 Mar 2009
Description: Wrapper object (cut down version) used for calculating fontSize in a cross browser manner so that
whether the style is set inline or by CSS and whether its in points, ems or percentages it will be calculated and
returned as an object which has the following properties
style: 100% // current style set on element for fontSize
px: 16px; // size in px that current style equates to
em: 1.2em // size in em that current style equates to
emu: 14px // size in px that 1em equates to on current element
*/


var CSSStyle = {

	// Functions for converting properties from camelCase to CSS-Property format and vice versa
	toCamelCase : function(name){
//		var camelCase = name.replace(/\-(\w)/g, function(all, letter){
//					return letter.toUpperCase();
//				  });
var camelCase = domPropSyntax(name);
return camelCase;
				  
	},

	toCSSProp : function(name){
		return name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
	},

	//returns the current style of an element with no conversions
	// call like 
	// var val = getStyle('myDiv','fontSize'); 
	// var val = getStyle('myDiv','font-size');
	getStyle : function(el,style){
		
		var elem = (typeof(el)=="string")?document.getElementById(el):el;
		var val = "", doc = (elem.ownerDocument||elem.document);

		// Make sure we have both formats for JS and CSS styles
		var camelCase = this.toCamelCase(style); //convert to camelCase
		var CSSProp = this.toCSSProp(style); //convert to css-property
		
		// Try for computed style first as this will return the actual value converted to px
		if (typeof doc.defaultView !== 'undefined' && typeof doc.defaultView.getComputedStyle !== 'undefined'){

			// We only require the word float for computedStyle
			if ( style.match( /float/i ) )
				style = "float";			

			var computedStyle = doc.defaultView.getComputedStyle( elem, null );
			//Standard Compliant function that will return the true computed size in px
			if ( computedStyle )
				ret = computedStyle.getPropertyValue( CSSProp );			

		} else if ( elem.currentStyle ) {			
			//IE only 
			ret = elem.currentStyle[ camelCase ];

		} else if( elem.style[ camelCase ] ){
			//Default to style if its been set by JS or inline styling
			ret = elem.style[ camelCase ];
		}

		return ret
	},


	//Cache to hold element sizes to prevent recursion
	fontCache : {},
	
	// Specific function designed for calculating true style size when styles set in %, em, px, pt etc
	// Will return an object with the current style, size in px, size in em, size that 1em equates to on this element
	getStyleSize : function(el, selector){
		var em,px,emu,curStyle,curStyleUnit,depx,cacheName;
		var auto=false,pauto=false;
		
		el = (typeof(el)=="string")?document.getElementById(el):el;

		// Try and get from cache if we have already calculated size details for this element
		cacheName = (el.id) ? el.id : (el.tagName=="BODY") ? "BODY" : ""; //we store in cache by ID or BODY otherwise we dont store

		// Get current/computed style details for element will return in px in Moz otherwise we have work to do
		// Do this before checking cache in case the style for the element we want to check has changed
		curStyle = this.getStyle(el, selector);
		
		if(cacheName && this.fontCache[cacheName]){
			// if style is still the same
			if(this.fontCache[cacheName].style == curStyle){
				return this.fontCache[cacheName];
			}
		}
		// Get the unit type from the style string e.g em,px,pt,%
		curStyleUnit = curStyle.match(/^\d*\.?\d*\s*([\w%]+)$/)[1];
		
		// calculate the size of 1em in relation to this element as we will use this in calculations
		if(curStyle!="1em"){
			emu = this.getPixelSize(el,"1em");
		}else{
			emu = px;
		}

		if(curStyleUnit=="em"){
			//convert em to px by checking size in relation to its parent fontSize as em is a relative style
			px = this.em2px(curStyle,el);
			em = curStyle;				
		}else{
			// Size is not currently in ems so calculate size in px then work out em size

			// If size is a % this means we need to calculate in relation to its parent being careful not to confuse auto
			// which will appear as the same % as its parent as an actual % of the parent!
			if(curStyleUnit=="%"){
				
				// If size is in % and we are on the body we calculate the correct size by checking the size in px of 1em on the BODY style
				if(el.tagName=="BODY"){
					px = this.getPixelSize(el,"1em");
					
				// otherwise we get the fontSize in px of this elements parent and work out the size in relation to that
				// this is where the cache comes in handy as if we have already checked these elements it will not re-calculate it unless
				// the elements style has been changed since it was cached.
				}else{
					var ps = this.getStyleSize(el.parentNode||el, selector);

					// If parent fontSize is also in % then we compare this elements style to the parents as if they are both the same eg 50%
					// then this may mean that the current elements style was set to "auto" which would mean it takes the same computed size
					// rather than 50% of the parents size. We set a flag if this is the case and will correct later on.
					if(/%$/.test(ps.style)){
						//if we are auto and parent is 50% then both will appear as 50% in the currentStyle
						if(ps.style==curStyle){
							// Parent style % is same as current elements so maybe auto was involved which we cannot extract unfortunatley 
							// so some testing is required to confirm this later on.
							pauto = true; //set flag so that later we may change according to futher tests							
						}
					}

					// We calculate px as a % of parent however this may change later if we can establish
					// that actually the % was inherited from the parent e.g auto
					var px = ((ps.px/100)*parseFloat(curStyle));
				}

			// If size is in pt then its a simple sum to convert to px
			}else if(curStyleUnit=="pt"){
				px = Math.round(1.3333*parseFloat(curStyle));					

			// If size is already in px
			}else if(curStyleUnit=="px"){
				px = parseFloat(curStyle);			

			//handle all other units
			}else{
				//get size in px by creating a div and then measuring it.				
				px = this.getPixelSize(el,curStyle);
			}							
		
			// If we earlier set a flag to tell us that we are dealing with a % style that could be "auto" we need
			// to double check our px size as we don't want to calculate the px as a % of the parent but instead
			// we should use the parents size. As there is no way of extracting "auto" we check the size of 1em on the child compare
			// it to the px size we currently have and if its different it means our original calculation was wrong.
			if(pauto){
				// Get size in px of 1em using current element as the parent.
				var cem = this.getPixelSize(el,"1em",true);
								
				// Check to see if the child 1em px == element px size
				if(cem==px){
					// It matches so our earlier % calculation was correct and this style is not actually "auto"
					auto = false;
				}else{
					// It doesn't match so we need to change our original px size after a double check to make sure 1em (child)
					// equals 1em on current. As this should be the case on auto.
					if(cem==emu){
						// If child 1em px = 1em px size on this element then we have an auto style and we change our px
						// to this value as our % calculation was wrong
						px = cem;
						auto = true;
					}
				}
			}
			
			// Convert px to em	
			em = this.px2em(parseFloat(px),el);		
		}	

		// Create our fontSize object
		var styleSize = {
			"style":curStyle,   // current/computed style will be px in standards compliant browsers but currentStyle in IE
			"px":px,  // size in px
			"em":em,  // size in em
			"emu":emu,  // size in px that 1em equates to on this element
			"auto":(auto)?"auto":"NA" // whether we identified this style as being "auto" (IE only)			
		}

		// Add this object to our cache if we can uniquely identify it (either by ID or unique tag e.g BODY)
		// which we checked earlier.
		if(cacheName){
			this.fontCache[cacheName] = styleSize;
		}		

		return styleSize;
	},


	// From Gimme.codeplex.com
	// This function calculates the size in px by creating a div with the required css style that needs converting
	// e.g if you need to convert em or % to px.
	// var px = getPixelSize('myDiv','20%')
	getPixelSize : function(el,styleVal,force){
		var px, el = (!el)?document.body:(typeof(el)=="string")?document.getElementById(el):el; //allow passing of ids OR element references
		var ue = el.tagName.toUpperCase();
		if(document.createElement){
			var div = document.createElement("div"); 
			div.style.position = "absolute"; 
			div.style.visibility = 'hidden'; //hide	
			//Apparently IE adds invisible space if this is not set
			div.style.lineHeight = '0'; 
			// Converting % or em to px has to be done in context of parent element; 
			// so do images as you cannot add the child div to an image.
			if(!force && ue!="BODY" && (/(%|em)$/.test(styleVal) || ue === "IMG")){		
				el = el.parentNode || el;
				div.style.height = styleVal;
			}else{
				div.style.borderStyle = 'solid';
				div.style.borderBottomWidth = '0';					
				if (styleVal == 'auto')
//>>>NO. We NEED the current size in px. We found a way to do this didn't we?
					styleVal = '';
				div.style.borderTopWidth = styleVal;
			}			
			//append hidden div to our element OR parent element if required
			el.appendChild(div);
			//measure size in px by getting offsetHeight
			px = div.offsetHeight;	
			//clean up
			el.removeChild(div); 
		}
	
		return px || 0;
	},


	// Convert px to em
	px2em : function(px,el){
		if(!px||px=="0")return 0;
		//calculate value of 1em which will be done in relation to this elements parent (handled in getPixelSize function)
		var em = this.getPixelSize(el,"1em");
		var val = (((px/em)*100)/100).toFixed(2);
		return val;
	},

	// Convert em to px
	em2px : function(em,el){
		if(!em||em=="0")return 0;
		em = parseFloat(em)+"em"; //may have passed in 2.3em or 2.3 or 2
		var px = this.getPixelSize(el,em);
		return px;
	}

}


function tstfunc1(s)
{
s += 'zxc';
return s;
}

var bFormLoaded = true;

