// form.js: automatic form validation and persistence script
// version 0.5, 03-Apr-2003
// written by Andrew Clover <and@doxdesk.com>, use freely

// Import event.js and then this module, either in the <head>
// or at a point in the <body> after all forms you want to
// affect. If you put it in the <head>, it will wait until the
// entire page is loaded before it starts.

// All forms with a 'name' attribute on the page will become
// persistent (remembering previously-submitted values) on
// browsers where JavaScript and cookies are enabled.

// Any fields where you specify a value of '*' are 'required'
// fields and must be filled in for the form to be submitted.
// Hidden fields and file upload fields are not persistant and
// cannot be made required.

// Change this line to set the error message for non-validating
// forms:

var form_error= 'Please fill in the following fields: ';

// Change this line to set how long persistent forms are
// remembered - default twenty weeks

// SC_MOD
//var form_cookieLife= 1000*60*60*24*7*20;
var form_cookieLife= 1000 * 60 * 10;

// end of user-servicable parts

var form_forms= new Array();
var form_bound= false;

// form_init: find forms in the page and bind to them

function form_init() {
  var i;
  if (form_bound) return;
  form_bound= true;
  for (i= document.forms.length; i-->0;) {
    if (document.forms[i].name!='') {
      form_bind(document.forms[i]);
      form_fromCookie(document.forms[i]);
  } }
}

// form_bind: attach our influence to a form and its required
//            fields, storing a list of these for later use

function form_bind(f) {
  var i, j, t;
  var required= new Array();
  for (i= f.elements.length; i-->0;) {
    t= f.elements[i].type;
    if (t=='text' || t=='password' || t=='textarea') {
      if (f.elements[i].value=='*') {
        event_addListener(f.elements[i], 'focus', form_focus);
        event_addListener(f.elements[i], 'blur', form_blur);
        required[required.length]= f.elements[i];
    } }
    if (t=='select-one' || t=='select-multiple') {
      for (j= f.elements[i].options.length; j-->0;) {
        if (f.elements[i].options[j].value=='*') {
          required[required.length]= f.elements[i];
    } } }
  }
  form_forms[form_forms.length]= new Array(f, required);
  event_addListener(f, 'submit', form_submit);
}

// form_submit: validate, allow submission and set cookie if OK

function form_submit(f, etype) {
  var i;
  var problems;
  for (i= form_forms.length; i-->0;) {
    if (form_forms[i][0]== f) {
      problems= form_validate(form_forms[i]);
      if (problems=="") {
        form_toCookie(f);
        return true;
      } else {
        alert(form_error+'\n'+problems);
        return false;
      }
  } }
  return true;
}

// form_focus: remove asterisk from required field when focused

function form_focus(ff, etype) {
  if (ff.value=='*') ff.value= '';
}

// form_blur: replace asterisk in required field if not filled in

function form_blur(ff, etype) {
  if (ff.value=='') ff.value= '*';
}

// form_validate: find unfilled required fields, returning a
//                string list of them, or '' if all is OK

function form_validate(arr) {
  var i, t, ix, valid;
  var f= arr[0];
  var required= arr[1];
  var problems= '';
  for (i= required.length; i-->0;) {
    t= required[i].type;
    valid= true;
    if (t=='text' || t=='password' || t=='textarea')
      valid= required[i].value!='*' && required[i].value!='';
    if (t=='select-one' || t=='select-multiple') {
      ix= required[i].selectedIndex;
      if (ix==-1) valid= false;
      else if (required[i].options[ix].value=='*') valid= false;
    }
    if (!valid) problems= problems+'  '+required[i].name+'\n';
  }
  return problems;
}

// form_toCookie: write form values into a cookie

function form_toCookie(f) {
  var cookid= 'form_'+f.name;
  var fields= form_encodeCookie(form_getFields(f));
  var now= new Date();
  var expiry= form_cookieDate(new Date(now.getTime()+form_cookieLife));
  var security= (document.location.protocol=='https:') ? ';secure' : '';
  document.cookie= cookid+'='+fields+';expires='+expiry+security;
}

function form_delCookie(name)
{
	  document.cookie= 'form_sc_form=;expires=-1';
}

// form_cookieDate: write date string conforming to Netscape's
//                  cookie definition (having to do this is stupid)

var form_days= new Array('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
var form_months= new Array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
                           'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');

function form_cookieDate(d) {
  return form_days[d.getDay()]+', '+form_encodeDec2(d.getDate())+'-'+
         form_months[d.getMonth()]+'-'+form_realYear(d)+' 12:00:00 GMT';
}

function form_realYear(d) {
  var y;
  if (d.getFullYear) return d.getFullYear();
  y= d.getYear();
  if (y>=2000) return y;
  return y+1900;
}

// form_fromCookie: get form values back out from cookie

function form_fromCookie(f) {
  var kv, ix, i;
  var cs= document.cookie.split(';');
  for (i= cs.length; i-->0;) {
    ix= 0;
    while (ix<cs[i].length && cs[i].charAt(ix)<'!')
      ix++;
    kv= cs[i].substring(ix).split('=', 2); // lose prefixed control chars
    if (kv.length==2)
      if (kv[0]== 'form_'+f.name)
        form_setFields(f, form_decodeCookie(kv[1]));
  }
}

// form_getFields: return a query-like string of form values. Pipes are
//                 used as separators instead of & and =, and +-encoding
//                 does not happen (as per JavaScript escape()).

function form_getFields(f) {
  var i, j, e, t;
  var q= '';
  for (i= f.elements.length; i-->0;) {
    e= f.elements[i];
    t= e.type;
    v= '';

    if (t=='text' || t=='password' || t=='textarea')
      q= form_queryAppend(q, e.name, e.value);

    if (t=='checkbox' || t=='radio') {
      if (e.checked) {
        q= form_queryAppend(q, e.name, (e.value!='') ? e.value : 'on');
    } }

    if (t=='select-one' || t=='select-multiple')
      for (j= e.options.length; j-->0;)
        if (e.options[j].selected)
          q= form_queryAppend(q, e.name, e.options[j].value);

  }
  return q;
}

function form_queryAppend(q, n, v) {
  if (v!='') {
    if (q!='') q= q+'|';
    q= q+escape(n)+'|'+escape(v);
  }
  return q;
}

// form_setFields: set fields from a getFields-style query string

function form_setFields(f, q) {
  var fields, i, j, k, e, t;

  fields= q.split('|');
  for (i=fields.length; i-->0;) {
    fields[i]= unescape(fields[i]);
  }

  for (i=f.elements.length; i-->0;) {
    e= f.elements[i];
    t= e.type;

    if (t=='checkbox' || t=='radio')
      e.checked= false;
    if (t=='select-multiple')
      for (k= e.options.length; k-->0;)
        e.options[k].selected= false;

    for (j= 0; j<fields.length-1; j= j+2) {
      if (fields[j]==e.name) {

        if (t=='text' || t=='password' || t=='textarea')
          e.value= fields[j+1];

        if (t=='checkbox' || t=='radio')
          if (e.value==fields[j+1] || (e.value=='' && fields[j+1]=='on'))
            e.checked= true;

        if (t=='select-one' || t=='select-multiple')
          for (k= e.options.length; k-->0;)
            if (e.options[k].value==fields[j+1])
              e.options[k].selected= true;

    } }
  }
}

// form_encodeCookie: remove characters unfit to go in a cookie's OPAQUE_STRING
//                    this is done by escaping with '!xx' (hex)

var form_invalid= '()<>@,;:\\"/[]?={}!';

function form_encodeCookie(s) {
  var i, c;
  var r= '';
  for (i= 0; i<s.length; i++) {
    c= s.charAt(i);
    if (form_invalid.indexOf(c)!=-1)
      r= r+'!'+form_encodeHex2(s.charCodeAt(i));
    else r= r+c;
  }
  return r;
}

// form_decodeCookie: convert a cookie's opaque string back

function form_decodeCookie(s) {
  var c;
  var r= '';
  for (i= 0; i<s.length; i++) {
    c= s.charAt(i);
    if (c=='!') {
      r= r+String.fromCharCode(form_decodeHex2(s.substring(i+1, i+3)));
      i= i+2;
    }
    else r= r+c;
  }
  return r;
}

// form_XXcodeXXXN: hex and decimal string coding, very tedious and should
//                  really be built into JavaScript

function form_encodeHex2(n) {
  var hex= '0123456789ABCDEF';
  if ((n&255)<16) return '0'+hex.charAt(n);
  return hex.charAt(Math.floor((n&255)/16))+hex.charAt((n&255)%16);
}

function form_decodeHex2(h) {
  var hex= '0123456789ABCDEF';
  var hn= hex.indexOf(h.charAt(0));
  var ln= hex.indexOf(h.charAt(1));
  if (hn!=-1 || ln!=-1)
    return hn*16+ln;
  return '?';
}

function form_encodeDec2(n) {
  if (n<10) return '0'+n;
  return ''+(n%100);
}

// call script init now if in <body>, or later if in <head>

if (document.body)
  form_init();
else
  event_addListener(window, 'load', form_init);
