/*
  tjForm jQuery Plugin
  by Randy Brandt
  
  Version: 0.85 19-Jan-2011 added readonly, optimized
  Version: 0.8 08-Dec-2010
  Version: 0.7 23-Oct-2010
  Copyright 2010 TiredJake.com

	support files:
		tjFormAjax.php makes calls
		tjConnect.php in user dir /inc holds db login
		
	
*/
/*
  Requires jQuery v1.5 or later; http://jquery.com
  Examples and docs:
    http://www.tiredjake.com/index.php?p=tjLog

  ****************************************************
  * future plans *
  *
  * include .data("tj") in init so we can't crash if left out
  * 
  validate return error count
	encryption
	rules for exp, date, etc
	
	add multiple table db support
  ****************************************************

  Licensed under the MIT license:
  http://www.opensource.org/licenses/mit-license.php

  Copyright (c) 2010 Randy Brandt, tiredjake.com
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:
  
  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.
  
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  }}}
*/
/*
  validate - validates form | e
  serialize | get - creates string from form | e
  load - loads form | e from db
  load+validate - load form from db and validate
  fill - fills form from JSON string; only updates specified fields
  save - validates form, saves to db if valid
  saveOnly - save form without validation
  saveSerial - save serialized string to db  
	
	isValid=validator(element); // single element
	isValid=validator("",{"op":"all"}); // all of form
	validator("",{"op":"init", "v.errDivID":"frm_bot"});

*/

/*
  optional init
  $().tjForm({"table":"test_table"});

  serialize
  $(form|e).tjForm();

  load/save
  element names must match table's field names
  multi-table coming later
  
  load
  $(form|e).tjForm("load",table);
  
  save
  $(form|e).tjForm("save",table); 
  $().tjForm("save", table, serialized string); 
*/

/* */
(function($) {
	var v = {
		formID: false, /* use id instead of name */
    curForm:"", /* current form: #frmAdmin */
    cmd:"", /* the full command */
    
    /* validation */
		errLabelClass: "errLbl",
		errFieldClass: "err",
    errClass:"", /* set at init */
		wantLabel: true,
    wantFocus: true,
    focusColor:"#ffffdf",

		errDiv: "#reqMsg",
		errText: "Please fix errors",
		wantBlur: true,
		fnMail: vEmail,
		fnCredit: vCredit,
		fnInput: vInput,
    fnPreVal: "",  /* callback before field validation */
    fnPostVal: "", /* callback after field validation */
    
		/* load/save/email */
    AJAX: "/~lib/tjFormAjax.php",
    emailer: "/~lib/tjEmail.php",
		fnFormSave: fn_tjForm,
		fnFormLoad: fn_tjFormLoad,
    id:"id", 					/* db id field name */
    idName:"frmGlossary", 					/* db id field name */
    wantEmail:false,	/* true = send email if success */
		dbfolder: "inc",
		gotElem:false
	};

  var aFS=new Array();
  var cntErrors;

$.fn.tjForm = function(cmdFull,opshuns) {
  v = $.extend(v, opshuns);
  cmd=(cmdFull)?cmdFull.substr(0,3):"ini";
  v.cmd=cmdFull;
	
  path=window.location.pathname;
	pos=1+path.lastIndexOf("/");
  v.base_dir=path.substr(0,pos);

  if (v.wantLabel) {
    v.errClass=v.errLabelClass;
  } else {
    v.errClass=v.errFieldClass;
  }

//console.log("tjForm.js w/opshuns",v);

  if (!(this.selector.length+this.length)) {
  // null collection - no meaning yet
    v.gotElem=false;
//console.warn("no elements");
  } else {
    v.gotElem=true;
  }
  
  switch (cmd) {
    case "ini": // initialize
      $(v.errDiv)
        .html(v.errText)
        .hide();
        
  //console.log("init");
      if (v.wantBlur) {
        $(v.curForm+" [class*=req]").blur(function() {
//console.log("blur:opshuns",v);          
//          isValid=$(this).tjForm("val"); // optional frm
          isValid=fnValidator(this);
          //console.log("regular:"+this.id+" "+isValid);
        });
  
        $(v.curForm+" [class*=req]").filter(":checkbox").change(function() {
//console.log("blur.cb:opshuns",v);          
          par=$(this).parents(".req");
          pID=par.attr("id");
          if (pID===undefined) {
  //console.log("cb tjForm: "+this.id+" >"+pID, par, this);
            isValid=$(this).tjForm("val"); // call our validator
          } else {
  //console.log("cb fieldsetValidate: "+this.id+" >"+pID, par, this);
            isValid=fieldsetValidate("#"+pID);
          }
        });
        
      }

      /* change input field color on focus
      originally applied class, but that didn't always work
      */
      if (v.wantFocus) {
        $(v.curForm+' input,textarea').focus(function() {
          var orig=$(this).css("background-color");
          $(this).data('tjBColor',orig);
          $(this).css("background-color",v.focusColor);
        });
        
        $(v.curForm+' input,textarea').blur(function() {
          var orig=$(this).data("tjBColor");
          $(this).css("background-color",orig);
        });
      }
      return true;
    break;
    
/*
 $.tjValidate({"init":true});
 
 $(e).tjValidate();
 $("#frm").tjValidate();
 
 $.tjValidate(element|"all");
 frm is optional form name
*/
    case "val":
      isValid= doValidate(this);
      break;
  
    case "ema":
      doEmail(v.curForm);
      break;
    
    case "get": // serialize
    case "ser":
      // walk thru jQuery collection
      this.each(function(){
        if (this.tagName=="FORM") {
  //console.log ("formSerialize",this.id,this);
          v.curForm="#"+this.id;
          sVal=formSerialize(v.curForm);
        } else {
  alert ("Forms only");
          return false; //break out
        }
      });
      return sVal;
      break;
    
/*
  save - validates form, saves to db if valid
  saveOnly - save form without validation
  saveSerial - save serialized string to db
*/
    case "sav": // save
      if (cmdFull=="save") {	//save
        isValid=doValidate(this);
        if (!isValid) {
          return false;
        }
      }

      if (v.gotElem) {
        this.each(function(){
          if (this.tagName=="FORM") {
            v.curForm="#"+this.id;
//console.log ("sav:", this.id, this, v.curForm);
//alert ("sav id, this, curForm", this.id, this, v.curForm);
            if (v.wantEmail) {
              doEmail(v.curForm); // send via email
            }
            d=$(v.curForm).data("tj");
//console.log("d",d);
            id=(d.tID)?d.tID:"new";
            sVal="s:"+d.tName+":"+v.id+":"+id;
//console.log("saveDB("+sVal+"...)");
            sVal+="&"+formSerialize(v.curForm);
						saveDB(sVal);       // store in db
          } else {
    alert ("Forms only!");
            return false; //break out
          }
        });
      }
      break;
    
    case "fil": // fill a form with JSON string
//alert("not done"); ///////////////////
        formFill (frm, sJSON);
      break;
    
    case 'loa': // load a form from db
			var fId=v.id;
//console.error("####load tjForm.js this:"+fId); // this
      if (v.gotElem) {
        this.each(function(){
          if (this.tagName=="FORM") {
//console.log("#load each this:",this);
//console.error("FORM id",v.id, $(this).attr(v.id));

            v.curForm="#"+$(this).attr(v.id); //"#"+this.id;
//console.error("v.curform",v.curForm,v.id);

            d=$(v.curForm).data("tj");  ///// undefined causes errors
	id="new";
		if (d) {			
            id=(d.tID)?d.tID:"new";
//console.log("tjForm d.id ", id);
		}
		
//console.log("293 tjForm "+v.base_dir, d, id);
            tjFormParms="tjForm=l:"+d.tName+">"+d.tSQL+":"+v.id+":"+id; //+"&nap=dyno";
            tjFormParms+="&tjPath="+".."+v.base_dir+v.dbfolder+"/";

$.log(tjFormParms, v); ///////////

            var jxhr=$.ajax({
              type: "POST",
              data: tjFormParms,
							dataType: "json",
              url: v.AJAX
            })
						.success(function(rez) {
							if (rez.status!="OK") {
//console.log("tjForm 'loa':",data);
//alert("aj~err: "+data);
								v.fnFormLoad(false,rez);
							} else {
$.log("tjForm v.fnFormLoad:");
$.log(rez);
$.log(rez.data);
								v.fnFormLoad(true,rez.data);
							}
						})
						.error(function(data) {
//console.log("ajax error:", data); //////////
              v.fnFormLoad(false,data);
						});

            return false; //break out
          } else {
    alert ("Forms only!");
            return false; //break out
          }
       }); // this.each
      }
      break;
  }
    
//console.log("EXIT NOW");
    return false;

}

/* */
function doValidate(jq) {
  // walk thru jQuery collection
  var isValid=true;
  jq.each(function(){
//console.log("tjV.each "+this.tagName,this);

    switch (this.tagName) {
      case "FORM":
        isVal=formValidate("#"+this.id);
        break;
      case "FIELDSET":
        isVal=fieldsetValidate("#"+this.id);
        break;
      default:
        isVal=fnValidator(this);        
    }
    isValid = isValid && isVal;
  });
  return isValid;
}

/*
  get form as serialized string
	ignore elements with readonly class
*/
function formSerialize(frm) {
  var a = [];
	$("input,select,textarea", frm)
	.not(".readonly,:checkbox,:radio,:password")
	.each(function() {
    n=(v.formID)? this.id : this.name;
    val= this.value;
    a.push({name: n, value: val});
  });

  $(frm+" input:radio").not(".readonly")
		.each(function() {
    n=this.name; // name required for groups
    if (this.checked) {
      val=this.value;
      a.push({name: n, value: val});
    }
  });

  $(frm+" input:checkbox").not(".readonly")
		.each(function() {
    n=(v.formID)? this.id : this.name;
    val=(this.checked) ? "true" :"false";
    a.push({name: n, value: val});
  });

  return $.param(a);
}


/* form element, so do all req class
*/
function formValidate(frm){
  aLen=aFS.length;
  cntErrors=0;
  for (i=0;i<aLen;i++) {
    aFS.pop(); // kill it
  }

  var isValid=true;
//console.error("formValidate",frm);
  $(frm+" [class*=req]").each (function() {

//console.log("fVal.each= "+this.tagName,this);

    switch (this.tagName) {
      case "FIELDSET":
        isVa=fieldsetValidate("#"+this.id);
        isValid=isValid && isVa;
        break;
      
      default: 
        var good2go=true;
        for (var a in aFS ) {
          if (aFS[a]==this.id) {
//console.error(this.id+" was already processed");
            good2go=false;
          }
        }
        if (good2go) {
          isVa=fnValidator(this);        
//console.log("isVa2: "+isVa);
          isValid=isValid && isVa;
//console.error("fVal:"+this.id+" "+isValid);
        }
    }
  });
  
  if (isValid) {
    $(v.errDiv).hide();
  } else {
    $(v.errDiv).show();
  }
//console.log("ERROR COUNT: "+cntErrors);
  return isValid;
}

/* fieldset typically checkboxes
*/
function fieldsetValidate(fs) {
  var cnt=0;
  var targ=(v.wantLabel)? $("[for="+fs.substr(1)+"]") : fs;

//console.log("fieldsetValidate",fs,targ,v);

  var minCount=0;
  var maxCount=9999;
  rules=$(fs).data("rules");
  if (rules && rules.substr(0,3)=="min") {
    minCount=rules.substr(3);    
  }

//console.log("fsVali rules:"+rules,frm);

  $(fs+" [class*=req]").each (function() {
//console.log("fs. "+this.tagName,this);
    aFS.push(this.id);
    isValy=fnValidator(this,true);
//console.log("fs."+this.name+" isVal: "+isValy,this);

    if (isValy==true) {
      cnt++;
    }
  });

  if (cnt<minCount || cnt>maxCount) {
    isValid=false;
  } else {
    isValid=true;    
  }

  if (isValid) {
//console.error("fieldset remove>"+v.errClass,"cnt:"+cnt,"min:"+minCount,"max:"+maxCount,targ,v);
		$(targ).removeClass(v.errClass);
    $(v.errDiv).hide();
  } else {
//console.error("fieldset add>"+v.errClass,"cnt:"+cnt,"min:"+minCount,"max:"+maxCount,targ,v);
		$(targ).addClass(v.errClass);
    $(v.errDiv).show();
    cntErrors++;
  }
  return isValid;
}

/*	single element validation
		returns isValid true/false
*/
function fnValidator(e,noCnt) {
		var isValid=true;
		// error display on label or element?
    var targ=(v.wantLabel)? $("[for="+e.id+"]") : e;
    var tag=e.tagName;
    if (!noCnt) {
      noCnt=false;
    }
//console.log ("fnValidator",e);
    sVal=$(e).val();

    x=$(e).filter(":checkbox");
    if (x.length) {
      tag="CB";
      isValid=(e.checked)?true:false;
//console.log ("checkbox:"+isValid,e.checked);
    } else {
      if ($(e).hasClass("req-email")) {
        isValid=v.fnMail(sVal);
  
      } else if ($(e).hasClass("req-credit")){
        cards = $(e).attr("class").replace(/.*?credit cc_(.*?)_/ig, "$1");
        isValid=v.fnCredit(sVal,cards,e);
  
      } else {
        
  var rules=$(e).data("rules");
  var minLen=0;
  var maxLen=9999;
    if (rules) {
      var r=rules.split(":");
      for ( var i in r ) {
        var cmd=r[i].substr(0,3);
        var iVal=r[i].substr(3);
        switch (cmd) {
          case "len":
            maxLen=iVal; //no break sets both
          case "min":
            minLen=iVal;
            break;
          case "max":
            maxLen=iVal;
            break;
        }
      }
    }
//console.log(e.name,rules,sVal,"min:"+minLen,"max:"+maxLen); ///////
        isValid=v.fnInput(sVal,minLen,maxLen);
      }
    }
    
    /* callback gets element, tag and current status
      tag is tagname, but cb for input checkbox
    */
    if (v.fnPostVal) {
      isValid=v.fnPostVal(e, tag, isValid);
//console.log("return fnPostVal:",isValid,v);
    }

		if (isValid) {
			$(targ).removeClass(v.errClass);
		} else {
//console.error("fnValidator add>"+v.errClass,"targ",targ,"v",v,"noC",noCnt);
  			$(targ).addClass(v.errClass);
      if (!noCnt) {
        cntErrors++;        
      }
		}
		return isValid;
	}

/* support functions that can be overridden */
function vEmail(sVal) {
	if (sVal.match(/^\b[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b$/i)) {
		return true;
	} else {
		return false;
	}
}

function vCredit(sVal, cards, e) {
	// visa/mastercard in place
	result = sVal.replace(/[^0-9]+/ig, "");
//console.log(result);
	if (result.match(/^(?:^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14})$)$/i)) {
		$(e).val(result);
		return true;
	} else {
		return false;
	}
}

/* text length
  this field is required, so if no rules, still need a length
*/
function vInput(sVal,min,max) {
  iLen=sVal.length;
//console.error("vInput:",iLen,min,max);
	if (iLen==0 || iLen<min || iLen>max) {
		return false;
	} else {
		return true;
	}
}

/* called by async AJAX save but can be overridden */
function fn_tjForm (success, msg){
  //console.log("fn_tjForm: "+msg);
  if (success) {
alert ("Form saved");
  } else {
alert ("Save failed\n"+msg);
  }
}

/* save serialized string to db */
function saveDB (serial) {	
  tjFormParms="tjForm="+serial;  
  tjFormParms+="&tjPath="+".."+v.base_dir+v.dbfolder+"/";

//console.log("saveDB preAJAX "+tjFormParms, v.AJAX, v); ///////////
//alert("saveDB-ajax:"+tjFormParms); /////////

	var jx=$.ajax({
		type: "POST",
		data: tjFormParms,
		dataType: "json",
		url: v.AJAX
	})
	.error(function(data) {
//console.log ("error 606",data, "responseText:", data.responseText);
	if(data.responseText) {
		//console.log("form.js 608", v.AJAX+"<br>"+data.responseText);	//plugMsg	
		v.fnFormSave(false,data);
	} else {
//alert ("success 610 fake error",data);		
//	getID();
	}
	})
	
	.success(function(data) {
$.log ("success 615",data);
			id=data.id;
$.log("612 ajax saveDB post v", v); ////////////////////
			$(v.curForm).tjData("tj",{tID:id});
			v.fnFormSave(true,data);

//alert("succ 615 xRow:"+xRow+" xAcct:"+xAcct+" xName:"+xName);
//alert ("success 615",data.responseText);
	});

/*
  var jxhr=$.ajax({
    type: "POST",
    data: "tjFormParms",
		dataType: "json",
    url: "/~lib/tjFormAjaxSave.php" /* v.AJAX 
  })

	.success(function(data) {
alert ("607 ajax success",data); ////////////////////
		if (data.status=="BAD") {
			v.fnFormSave(false,data);
		} else {
			id=data.id;
//console.log("612 ajax saveDB post v", v); ////////////////////
			$(v.curForm).tjData("tj",{tID:id});
			v.fnFormSave(true,data);
		}
	})
	
	.error(function(data) {
//console.log("ajax error",data); ////////////////////
alert("jquery.tjForm error @ 620");
		v.fnFormSave(false,data);
	});
	*/
}

/* send email summary */
function doEmail(frm) {
  sVal=formSerialize(frm);
  
  /*  body has to be last of parms
      * indicates serial data follows
  */
  tjEmailParms="body=*&"+sVal;

  $.ajax({
    type: "POST",
    data: tjEmailParms,
		dataType: "json",
    url: v.emailer,
    error: function (data) {
//console.log("email error:",data);
    },
    success: function(data) {
      if (data.status!="OK") {
				
alert("Email failure: "+data.id);
      } else {
//alert("email "+data);
      }
    }
  });
}

/* called by async AJAX load but can be overridden */
function fn_tjFormLoad (success, sJSON){

//$.log("fn_tjFormLoad: ",success,v,sJSON); //////////////
  if (success) {
//$.log("call formFill");
    formFill(v.curForm, sJSON);
    if ((v.cmd).substr(0,6)=="load+v") {
      isVal=formValidate(v.curForm);
    }
  } else {
    //console.log ("Unable to load db record;\n"+sJSON);
  }
}


/* pass form name and JSON data  */
function formFill (frm, fld) {

//console.log ("tj formFill:", frm, fld); ////////////

//  var fld = JSON.parse(sJSON);
  //console.log ("tj formFill fld:", fld);
//  var id=(fld[v.id])?fld[v.id]:-1; "new"
  // populate the form; first, set id
  $(frm).tjData("tj",{tID:fld[v.id]});
    
  //clear passwords
  $(frm+" #password").each(function (i) {
    $(this).val("");
  });

  // input
  $(frm+' input').each(function (i) {
    if (fld[this.name]){
//console.warn ("fld: "+[this.name],fld[this.name]);
      
      if (fld[this.name].raw != undefined) {
/* ?? sVal=(fld[this.name]+""!="")? fld[this.name][0] : "%%"; */
        sVal=fld[this.name].val;
        sValRaw=fld[this.name].raw;
$.log (fld[this.name],sVal,sValRaw,{type:"info"}); ////
        
        // dates
        if (!sVal) {
          sVal="";	//sValRaw;
        }
        
        $(this).attr("data-raw",sValRaw);
        $(this).attr("data-prior",sVal);
      } else {
        sVal=fld[this.name];
        sValOrig="";
      }
    } else {
      sVal="";
    }
    $(this).val(sVal);
  });
  
  $(frm+' textarea').each(function (i) {
    $(this).val(fld[this.name]);
  });
  
  // checkboxes
  $(frm+" :checkbox").each(function (i) {
//alert(this.name+":"+fld[this.name]);//////////////////
    $(this).attr('checked', (fld[this.name].substr(0,1)=="t")?true:false);
    $(this).val(fld[this.name]);
  });
  
  // option select dropdown by matching val
  $(frm+' select').each(function (i) {
    $(this).val(fld[this.id]);
  });

/*        
  // init data-prior for formatted fields for change detection
  $(frm+" [data-format]").each(function (i) {
    o=$('#'+this.id);
    sValue=o.val();
    o.attr("data-prior",sValue);
  });
*/  
}

	function plugMsg(where, theMsg) {
		var theErr="<div id='theErr' style='border: 4px double blue; margin:10px 20px; background-color:white; padding:20px; color:red;'>"+where+"<br>"+theMsg+"</div>";
		$("body").append(theErr);
alert(where);
	}

})(jQuery);
