/*
 * jQuery Validate plugin v1.0
 * File Date: 10/23/2009
 *
 * Copyright (c) 2009 Jim Salyer
 * Licensed under the MIT License:
 *   http://www.opensource.org/licenses/mit-license.php
 */

jQuery.fn.extend({
  validate: function(locals)
  {
    // create the global properties off of defaults and the provided parameters
    var globals = {
      mapURL: "",                     // URL of validation map XML file
      onAfterValidate: function(){},  // for running pre-validation actions (per field)
      onBeforeValidate: function(){}, // for running post-validation actions (per field)
      onError: function(errors){},    // what to do when a validation error occurs (per field)
      onSuccess: function(){},        // what to do when a field successfully validates
      scriptURL: ""                   // URL of backend validation script
    };
    jQuery.extend(globals, locals);
    
    // get the elements to apply this plugin to and make sure the file paths are present
    var thisObj = jQuery(this);
    if (globals.mapURL == "" || globals.scriptURL == "") return thisObj;
    
    // run the Ajax request to retrieve the XML validation map
    jQuery.ajax({
      type: "GET",
      url: globals.mapURL,
      dataType: "text", // so we can use the XML code for the individual field validations
      success: function(data, txt)
      {
        // initialize the global variables using the supplied XML
        var xml = jQuery(data);
        var sync = eval(xml.find("map").attr("sync"));
        var map = [];
        var messages = [];
        
        // create the validation map fields using the supplied XML
        xml.find("field").each(function()
        {
          var field = jQuery(this);
          var checks = [];
          
          field.find("check").each(function()
          {
            var check = jQuery(this);
            checks.push({
              id: check.attr("id"),
              params: check.attr("params"),
              message: check.attr("message")
            });
          });
          
          map.push({
            id: field.attr("id"),
            "checks": checks,
            display: field.attr("display")
          });
        });
        
        // create the validation map message overrides (map wide) using the supplied XML
        xml.find("message").each(function()
        {
          var message = jQuery(this);
          messages.push({
            id: message.attr("id"),
            text: message.attr("text")
          });
        });
        
        // loop through the given elements
        thisObj.each(function(fi)
        {
          // 1. create a reference to the current form
          // 2. create a field to hold the supplied XML
          var frm = jQuery(this)
          if (frm.get(0).tagName.toLowerCase() != "form") return true;
          frm.append(jQuery('<input type="hidden" name="validate_xml" id="validate_xml' + fi + '" value="" />').val(data)).append('<input type="hidden" name="validate_field" id="validate_field' + fi + '" value="" />');
          
          // setup the validation by looping through the map
          jQuery.each(map, function(i, n)
          {
            // loop through the fields with the current map item's name
            frm.find(":input[name=" + n.id + "]").each(function()
            {
              // store a reference to the current field and initialize the list of event handlers
              var el = jQuery(this);
              var handlers = [];
              
              // add the appropriate event handlers based upon field type
              if (!el.is(":checkbox")) handlers.push("blur");
              if (el.is(":checkbox, :radio"))
                handlers.push("click");
              else if (el.is("select, :file"))
                handlers.push("change");
              
              // internal function for checking if a value is undefined, null or a blank string
              var blank = function(val)
              {
                return (typeof(val) == "undefined" || val == null || val == "");
              };
              
              // internal validation method
              var validation = function()
              {
                // make sure the field's value has changed since it was last checked
                var val = value(el);
                if (val == el.data("validate_last_value"))
                  return;
                else
                  frm.find(":input[name=" + n.id + "]").data("validate_last_value", val);
                
                // run any pre-validation actions and store the field's name for backend validation use
                globals.onBeforeValidate.call(el);
                frm.find(":input[name=validate_field]").val(n.id);
                
                // serialize the form elements for submission (making sure to include file fields)
                var serialized = frm.serialize();
                frm.find(":file").each(function()
                {
                  var el = $(this);
                  if (serialized.length > 0) serialized += "&";
                  serialized += el.attr("name") + "=" + el.val();
                });
                
                // post the form's information and XML to the given backend validation script
                jQuery.post(globals.scriptURL, serialized, function(data)
                {
                  // run any post-validation actions
                  globals.onAfterValidate.call(el);
                  if (!blank(data))
                    globals.onError.call(el, data.split("\n"));
                  else
                    globals.onSuccess.call(el);
                });
              };
              
              // get the given field's value
              var value = function(fld)
              {
                var val = "";
                if (fld.is(":checkbox"))
                {
                  // create a list of the selected values for checkboxes
                  fld.parents("form").find(":input[name=" + fld.attr("name") + "]").each(function()
                  {
                    var el = jQuery(this);
                    if (el.is(":checked"))
                    {
                      if (val.length > 0) val += ", ";
                      val += el.val();
                    }
                  });
                }
                else if (fld.is(":radio"))
                {
                  // get the checked value for radio buttons
                  fld.parents("form").find(":input[name=" + fld.attr("name") + "]").each(function()
                  {
                    var el = jQuery(this);
                    if (el.is(":checked"))
                    {
                      val = el.val();
                      return false;
                    }
                  });
                }
                else
                  val = fld.val(); // get the value of every other field type
                return jQuery.trim(val); // return the trimmed field value
              };
              
              // bind the validation method, through the event handlers, to the current field
              jQuery.each(handlers, function(i2, n2)
              {
                el.bind(n2, validation);
              }); 
            });
          });
        });
      }
    });
    return thisObj; // keep the jQuery chain alive
  }
});