// Fieldset object and functions
// NB: requires CSS class .invalid

function Fset(hdrs) {
  if (!hdrs)
   hdrs = new Array();
 this.hary = hdrs;
 this.hct = hdrs.length;
 this.hdr43 = new Array();

 this.geth = function(hno,pos) {
   if (pos==1 && this.hary[hno][2]==43)
    return this.hary[hno][4]['fld'];
   else	
    return this.hary[hno][pos];
 }

// .mklb - makes a label td from header no.
 this.mklb = function(hno) {
  var hdr = this.hary[hno];
  var td = d_mk("td",'',unescape(hdr[0]));
   if (hdr[4]["tdclass"]) d_cn(td,hdr[4]["tdclass"]); 
  return td;
 }
   
// .mkfm - makes a formatted string for submission to the XHR object, or false if a field is invalid
 this.mkfm = function(rw) {
  var fm = '';
  var inps=rw.getElementsByTagName('INPUT');
  var txts=rw.getElementsByTagName('TEXTAREA');
  var sels=rw.getElementsByTagName('SELECT');
   for(i=0;i<inps.length;i++) {
     if (inps[i].className == 'invalid')
	  return false;						
    var ptr = inps[i].id.indexOf(":");
    var typ = inps[i].id.substr(ptr+1);
     if (typ=='40')
	  fm += inps[i].name + '=' + this.conv(inps[i].checked,typ) + '&';
	 else 
	  fm += inps[i].name + '=' + this.conv(inps[i].value,typ) + '&';
   }
   for(i=0;i<txts.length;i++) {
     if (txts[i].className == 'invalid')
	  return false;
    var ptr = txts[i].id.indexOf(":");
    var typ = txts[i].id.substr(ptr+1);
    fm += txts[i].name + '=' + this.conv(txts[i].value,typ) + '&';
   }
   for(i=0;i<sels.length;i++) {
     if (sels[i].className == 'invalid')
	  return false;
    var ptr = sels[i].id.indexOf(":");
    var typ = sels[i].id.substr(ptr+1);
    fm += sels[i].name + '=' + this.conv(sels[i].value,typ) + '&';
   }
  return fm.substr(0,fm.length-1);
 }

// .mkdt makes a td element formatted correctly, from the header number hno (index to the header array)
 this.mkdt = function(v,hno) { 
  var hdr = this.hary[hno];
   if (!v && hdr[4]["default"])
    v = hdr[4]["default"];
  v = unescape(v);
  var td;
  var ttl;
   if (hdr[2]>12 && hdr[2]<16 && hdr[4]["cols"]) {
     if (v.length > hdr[4]["cols"]) {
	  ttl = v;
	  v = v.substr(0,hdr[4]["cols"]-3) + '...';
	 }
   }
   switch(hdr[2]) {
    case 3:		// money
	 td = d_mk("td",'',fst_curr(v,hdr));
	 d_ss(td,'textAlign','right');
	 break;
    case 4:	 	// account format, note	hdr[4]["debit"]
      if (hdr[4]["debit"]) {
	    if (v<0)
	     td = d_mk("td",'',fst_curr(Math.abs(v),hdr));
	    else
   	     td = d_mk("td");
	  } else {
	    if (v>=0)
	     td = d_mk("td",'',fst_curr(v,hdr));
	    else
   	     td = d_mk("td");
	  }
	 d_ss(td,'textAlign','right');
	 break;
	case 7:			// percentage
	 td = d_mk("td",'',fst_curr(v,hdr)+'%');
	 d_ss(td,'textAlign','right');
	 break;
    case 15:		// uppercase
     td = d_mk("td",'',v.toUpperCase());	 
	 break;
    case 21:		// date
//	  if (v==0 || v=='0')
//	   v = false;	
     td = d_mk("td",'',fst_date(v,false));
	 break;
    case 22:		// date + time	
	  if (v==0 || v=='0')
	   v = false;	
     td = d_mk("td",'',fst_date(v,true));
	 break;
    case 23:		// hh:mm
     var h = Math.round(v/3600,0);
	 var m = Math.round(v/60-h*60,0);
	  if (m<10)
	   m = '0' + m;
	 td = d_mk("td",'',h+':'+m);
	 d_ss(td,'textAlign','right');
	 break;
    case 24:		// linked URL
     var td = d_mk("td");
	 var w = (hdr[4]["href"]) ? hdr[4]["href"] + v : v;
	  if (hdr[4]["link"])
	   d_mk("a",td,hdr[4]["link"],'',{"href":w});
	  else 
	   d_mk("a",td,v,'',{"href":w});
	 break;
    case 25:		// spanned data
     var td = d_mk("td");
	 var sp = d_mk("span",td,v);
	  if (hdr[4]["class"])
	   d_cn(sp,hdr[4]["class"]);
      if (hdr[4]["onclick"])
	   sp.onClick = hdr[4]["onclick"];
	 break;
    case 26:		// centered
	 td = d_mk("td",'',v);
	 d_ss(td,'textAlign','center');
	 break;
    case 27:		// right-aligned
	 td = d_mk("td",'',v);
	 d_ss(td,'textAlign','right');
	 break;
    case 28: case 29:		// password	
     td = d_mk("td",'','****');	
	 break;
	case 30: case 31: case 32:		// textareas 
	  if (v.length>200) {
	   v = v.substr(0,200) + '...';
	  }
	 td = d_mk("td");
	 di = d_mk("div",td);
	 di.innerHTML = v;
	 break; 
    case 40:		// boolean
      if (v!=0) {
	    if (hdr[4]["istrue"])
         td = d_mk("td",'',hdr[4]["istrue"]);
	    else	
         td = d_mk("td",'','yes');
	  } else {
	    if (hdr[4]["isfalse"])
         td = d_mk("td",'',hdr[4]["isfalse"]);
	    else	
         td = d_mk("td",'','no');
	  }	
	 d_ss(td,'textAlign','center');
	 break;
    case 41: 		// single select from list
     var t = '(none)';
	  if (v) {
        if (hdr[4]["values"]) {
	     var i = fst_log2(v);
	      if (hdr[4]["values"][i])
	       t = hdr[4]["values"][i];
  	    }
	  }
     td = d_mk("td",'',t);
	 break;
    case 42:		// multi-select
     var t = '';	
	  if (v && v!= '0') {
        if (hdr[4]["values"]) {
	      for (var i=0;i<hdr[4]["values"].length;i++) {
		    if (fst_pow2(i) & v) {
		      if (t.length)
			   t += ', ';
			 t += hdr[4]["values"][i];  
		    }
		  }
  	    } else 
	     t = '(none)';
	  } else 
	   t = '(none)';
     td = d_mk("td",'',t);
	 break;
    default:
     td = d_mk("td",'',v);
   } 
   if (hdr[4]["tdclass"]) d_cn(td,hdr[4]["tdclass"]); 
   if (ttl) td.title = ttl;
  return td;
 }

// .mkfd makes a td element, containing an appropriate input element, from the header number hno (index to the header array)
 this.mkfd = function(v,hno) { 
  var hdr = this.hary[hno];
   if ((v=='%a0') && hdr[4]["default"])
    v = hdr[4]["default"];
  v = (v == '%a0') ? '' : unescape(v);
   if (1 & hdr[3]) {	// it's editable
    var td = d_mk("td");
    d_sa(td,'vAlign','top');
    var act;
     switch(hdr[2]) { 
	  case 3:		// note: case 4 should be non-editable
	  case 7:
	    if (hdr[4]["curr"])
	     d_st(td,hdr[4]["curr"]);
       act = d_mk("input",td,'','',{"type":"text"});
	   act.size = (hdr[4]["cols"]) ? hdr[4]["cols"] : 8;
       var c='*'+(v*0.01);var j=c.indexOf('.');if(j>-1){
       c+='0';c=c.substr(1,j+2);}else c=c.substr(1)+'.00'; 
	   act.value = c;
	   d_ss(act,'textAlign','right');
	     if (hdr[2]==7)
		  d_st(td,'%');
	   break;
      case 21:		// date
       act = d_mk("input",td,'','',{"type":"text"});
	   act.size = 12;
	   act.maxLength = 11;
	   act.value = fst_date(v,false,true);
	   break;
      case 22:		// date + time	
       act = d_mk("input",td,'','',{"type":"text"});
	   act.size = 21;
	   act.maxLength = 20;
	   act.value = fst_date(v,true,true);
	   break;
      case 23:
       var h = Math.round(v/3600,0);
	   var m = Math.round(v/60-h*60,0);
	    if (m<10)
	     m = '0' + m;
	   act = d_mk("input",td,h+':'+m,'',{"type":"text"});
	   d_ss(act,'textAlign','right');
	   break;
      case 28: case 29:
       act = d_mk("input",td,'','',{"type":"password"});
	   act.size = (hdr[4]["cols"]) ? hdr[4]["cols"] : 8;
	   act.value = v;
	   break;
      case 30: case 31: case 32:
       act = d_mk("textarea",td);
	   act.rows = (hdr[4]["rows"]) ? hdr[4]["rows"] : 5;
	   act.cols = (hdr[4]["cols"]) ? hdr[4]["cols"] : 8;
	   act.value = v;
	   break;
      case 40:	
       act = d_mk("input",td,'','',{"type":"checkbox"});	
        if (v!=0) {
		 act.checked = true;
		} 
	   break;
      case 41: case 42:
       act = d_mk("select",td);
	    if (hdr[2]==42)	{	// not sure this works in IE
		 act.multiple = true;
		 act.size = 5;
		} 
        for (var i=0; i<hdr[4]["values"].length; i++) {
	     var opt = d_mk("option",act,hdr[4]["values"][i]);
		 opt.value = fst_pow2(i);
		  if (fst_pow2(i) & v)
		   opt.selected = 'selected';
	    }
	   break;
      case 43:
       act = d_mk("select",td);
        if (!this.hdr43[hdr[1]])
         fst_get43(this.hdr43,hdr,v);
        else
         fst_set43(this.hdr43[hdr[1]],hdr[1],v,act);
	   break;
	  case 51:		// special case of a file input field, not normally permitted in the table context
        act = d_mk("input",td,'','',{"type":"file"});
        break;
      default:
       act = d_mk("input",td,'','',{"type":"text"});
	   act.size = (hdr[4]["cols"]) ? hdr[4]["cols"] : 8;
	   act.value = v;
     }
	 if (hdr[4]["maxlen"])
	  act.maxLength = hdr[4]["maxlen"]; 
    act.name=hdr[1]; 
    act.id=hdr[1] + ':' + hdr[2];
    act.onblur = function() {var x=fst_vali(this.value,hdr); if(x){d_cn(this,'invalid');d_sa(this,'title',x)}else{d_cn(this,'');d_sa(this,'title','');if(hdr[4]["action"])eval(unescape(hdr[4]["action"]));}};
    var x = fst_vali(act.value,hdr);
     if (x) {d_cn(act,'invalid');d_sa(act,'title',x);}
	 if (hdr[4]["tdclass"]) d_cn(td,hdr[4]["tdclass"]);
	 if (hdr[4]["hint"]) d_st(td,' '+hdr[4]["hint"]); 
    return td;
   } else {
     if (!v && hdr[2]<40)
      v = unescape('%a0');
    var td = this.mkdt(v,hno);
     if (2 & hdr[3])	// it's mandatory 
      d_mk("input",td,'','',{"type":"hidden","name":hdr[1],"value":v}); 
    return td;  
   }	
 }

// .conv converts the data to the form required by the database and escapes it so it's suitable to carry in a POST
 this.conv = function(v,typ) {
  var r;
   switch(typ) {
    case '3': case '4': case '7':
     r = Math.round(v*100);
	 break;
    case '15':
     r = v.toUpperCase();
	 break;
    case '21': case '22':
     var d = Date.parse(v);
	 r = (isNaN(d)) ? 0 : d*0.001;
	 break;
    case '23':
     var col = v.indexOf(':');
	  if (col>-1) {
	   var h = v.substring(0,col);
	   var m = v.substring(col+1);
	   r = h*3600+m*60;
	  } else
	   r = 0;
	 break;
    case '28':
     r = md5(v);
	 break;
    case '40':
     r = (v) ? 1 : 0;
	 break;
    default:
     r = v;	
   }
  return escape(r); 
 }

} // end Fset object
 
 function fst_vali(v,hdr) {
   if (hdr[2]>=40)
    return false;
   if ((hdr[2]>9) && (2 & hdr[3]) && (!v))
    return 'Mandatory field cannot be empty';
/* */
   if (hdr[4]["minlen"] && (v.length<hdr[4]["minlen"]))
    return 'Field must contain at least ' + hdr[4]["minlen"] + ' characters';
   if (hdr[4]["maxlen"] && (v.length>hdr[4]["maxlen"]))
    return 'Field must not contain more than ' + hdr[4]["maxlen"] + ' characters';
   if ((hdr[2]>0) && (hdr[2]<5)) {
     if (hdr[4]["min"] && (v<hdr[4]["min"]))
	  return 'Value must be at least ' + hdr[4]["min"];
     if (hdr[4]["max"] && (v>hdr[4]["max"]))
	  return 'Value cannot be more than ' + hdr[4]["max"];
   } else if ((hdr[2]==5) && (v<1))
    return 'Invalid value';
/* */
   switch (hdr[2]) {
    case 0:
	 return false;
	case 1:
	 return (v.match(/^-?\d+$/)) ? false : 'Invalid characters: integer numbers only';
	case 2:
	case 3:
	case 4:
	case 6:		// double
	case 7:		// percentage
	 return (v.match(/^-?\d+(\.\d+)?$/)) ? false : 'Invalid characters: numbers only';
	case 5:
	 return (v.match(/^[0-9]*$/)) ? false : 'Invalid characters: positive integers only';
	case 10: 
	 return (v.match(/^[a-zA-Z]*$/)) ? false : 'Invalid characters: letters only';
	case 11:
	 return (v.match(/^[a-zA-Z ]*$/)) ? false : 'Invalid characters: letters and space only';
	case 12:
	 return (v.match(/^[0-9a-zA-Z ]*$/)) ? false : 'Invalid characters: letters, digits and space only';
	case 13:
	 return (v.match(/^[0-9a-zA-Z \-\'\"\.,\/()&]*$/)) ? false : 'Invalid characters: address characters only';	   //'
	case 14:
	case 15:
	case 31:
	 return (v.match(/<.*>/)) ? 'HTML tags not permitted' : false;
	case 16:
	 return (v.match(/^[0-9 +\-()]*$/)) ? false : 'Invalid characters: telephone numbers only';
    case 20:
	 return (v.match(/^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[0-9a-zA-Z]{1,9})$/)) ? false : 'Invalid e-mail address';
	case 21:
	  if (!v || v=='0' || v=='---' || v.substr(0,6)=='00 mth')
	   return false;
	  else 
	   return (v.match(/^([0-3][0-9] (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9]{4}|---)$/i)) ? false : 'Invalid characters: date only'; 
	case 22:
	 return (v.match(/^([0-3][0-9] (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9]{4} [0-2][0-9]:[0-5][0-9]:[0-5][0-9]|---)$/i)) ? false : 'Invalid characters: date and time only'; 
	case 23:
	 return (v.match(/^[0-9]*:[0-5][0-9]$/i)) ? false : 'Invalid characters: hh:mm only'; 
//	case 28: case 29: // password - strong
//	 return (v.match(/^(?=.*[0-9]+.*)(?=.*[a-z]+.*)(?=.*[A-Z]+.*)[0-9a-zA-Z]{6,}$/)) ? false : 'Password must have at least one lowercase and one uppercase letter, and one digit, and must have at least 6 characters';
	case 32: 
	 return (v.match(/<.*=.*>/)) ? 'HTML attributes not permitted' : false;
	default:
	 return false; 
   }
 }

// format helper functions
function fst_curr(v,hdr) {
 var c='*'+(v*0.01);var j=c.indexOf('.');if(j>-1){
 c+='0';c=c.substr(1,j+2);}else c=c.substr(1)+'.00'; 
 for(var i=c.length-6;i>0;i-=3)c=c.substr(0,i)+','+ c.substr(i);
 if(hdr[4]["curr"])c=hdr[4]["curr"]+c;return c;}
function fst_cur2(v,sym) {
 return fst_curr(parseInt(Math.round(v,0)),[null,null,null,null,{'curr':sym}]);}
function fst_log2(v) {return Math.round((Math.log(v) / Math.LN2),0)}
function fst_nl2br(v) {return v.replace(/\n/g, "<br />");}
function fst_pow2(i) {return Math.round(Math.pow(2,i),0)}
function fst_date(v,w,x) {var mths=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
 var d = new Date();if(v!=''){if(v!=0 && v!='0' && v!='%a0')d.setTime(v*1000);else{if(x)return '00 mth '+d.getFullYear();else return '---';}}if (d.getDate()<10)var t='0'+d.getDate()+' '+mths[d.getMonth()]+' '+d.getFullYear();
 else var t=d.getDate()+' '+mths[d.getMonth()]+' '+d.getFullYear();if(w){t+=' ';if(d.getHours()<10)t+='0';
 t+=d.getHours()+':';if(d.getMinutes()<10)t+='0';t+=d.getMinutes()+':';if(d.getSeconds()<10)t+='0';t+=d.getSeconds();}return t;} 

function fst_get43(hdr43,hdr,v) {
 var xhr = new Xhrwrap();
 xhr.post(hdr[4]['script'],'hdr=' + hdr[1],function(rtxt){rdat = eval(rtxt);hdri=rdat.shift();hdr43[hdri]=rdat;fst_set43(rdat,hdri,v);});
 xhr = null;
} 

function fst_set43(optary,hdr,v,act) {
  if (!act)
   var act = d_ge(hdr+':43');
 var noopt = optary.length;
  for (var i=0; i<noopt; i++) {
   var opt = d_mk("option",act,optary[i][1]);
   opt.value = optary[i][0];
	if (optary[i][1] == v)
     opt.selected = 'selected';
  }
}