
/* jQuery FormValidator plugin by David Johnson


How to use:
First, construct a form in HTML as follows:

<form id="form_id" class="jq-form" action="controller/action" method="post">
<table>
    <tr class="jq-form-group">
        <td>
            Email Address:
        </td>
        <td>
            <input type="text" id="form_email_address" name="email" class="jq-form-el req-email" value="<?= $email_value; ?>">

            <div class="jq-form-error">
                Error message goes here
            </div>
        </td>
    </tr>
</table>

<button class="button-submit">Save</button>

</form>

To submit the form via a button that is not within the form element:
<script type="text/javascript">
$('#button_id').bind('click', function() {
    form_name.formSubmit();
});
</script>

form_name = new jQuery.FormValidator({
    form_id: 'form_id',
    // Specify a button outside of the form element used to submit the form
    button: 'button_id',
    // Special function to be called before the form is submitted to the server *optional
    onsubmit: function( obj ) {
        doSomething();
    },
    // Callback function after the form is submitted to the server
    callback: function( req ) {
        if (req && req.success) {
           doSomething();
        }
    }
});

*/


(function($){


  $.fn.form = function( params, args ) {

        if (typeof(params) == 'object') {


            var defaults = { // set the defaults
                    striped: true, // defaults alternating grid rows to class 'grid-stripe'
                    grouping: false, // do not group the results
                    paginate: false, // do not paginate the results
                    filter: false, // do not create a filter mechanism
                    minimizable: false // do not allow the user to minimize the grid
            };

            params = $.extend(defaults, params);

           return this.each(function(){
                $(this).data('form_obj', new FormValidator(this, params) );
            });


        } else if (typeof(params) == 'string') {
            if (params == 'get') {
                return this.grid_obj;
            }
            return this.each(function(){
                switch(params) {
                        case 'reload':
                                this.grid_obj.reload();
                        break;
                        case 'set':
                           for (var arg in args){
                               this.grid_obj.params[arg] = args[arg];
                           }
                        break;
                }
            });


        }


  };



// Initialize the FormValidator!
var FormValidator = function( el, params ) {
    // Place the defined parameters within the scope of the object
    this.params = params;
    // If a callback isn't specified, use the default callback
    if (!this.params.callback) { this.params.callback = this.defaultCallback; }
    this.callback = this.params.callback;
    this.init( el );

    //set the form as clean
    this.dirty = false;

    var obj = this;
    //when an element is changed, set it to dirty
    $(".jq-form-el", this.form).each(function(){
        var el = $(this);
        el.change(function(){
            obj.dirty = true;
        });
    });
};

FormValidator.prototype.init = function( el ) {
    this.submitting = false;
    this.confirmed = false;
	this.submitted = false;
    // Get the form element
    this.form = $( el );
    // Get the action of the form. This is where we post the data
    this.action = this.form.attr('action');
    var obj = this;

    // Find the button within the form that is used to submit the form and bind the onclick
    $( '.button-submit', this.form ).unbind().bind( 'click', function() {
        obj.formSubmit();
    });

    // If params.button_id is defined, give that button an onclick to submit the form
    if (this.params.button_id) {
        $( '#'+this.params.button_id ).unbind().bind( 'click', function() {
            obj.formSubmit();
        });
    }
    // If using <button type="submit">, use this objects form submission functionality and return false
    this.form.submit( function() {
        obj.formSubmit();
        return false;
    });
};

// Submit the form!
FormValidator.prototype.formSubmit = function() {

    if (this.submitting == false) {
        this.submitting = true;
        var obj = this;
        // Create an array to store the form data
        this.data = { };
        // Set the hasErrors flag to 0
        this.hasErrors = 0;
        // Create an array to store the form errors
        this.errors = new Array();

        // Loop through each of the form groups
        $( '.jq-form-group', this.form ).each( function() {
            // Form elements are referenced by the class "jq-form-el"
            // Elements without this class will be ignored
            obj.formEls = $( '.jq-form-el', this );
            // Reset the groupErrors counter to 0
            obj.groupErrors = 0;
            // Loop through each element and run the obj.validate() function
            for (var i=0; i<obj.formEls.length; i++) {
                obj.validate( obj.formEls[i], this );
            }

            // If errors have been found, let's handle them
            if (obj.groupErrors > 0) {
                // Get the error text by getting the html of the .jq-form-error element
                var error_class = '.jq-form-error';
                if ($(this).hasClass('password-group') && $('.req-password', this).val() != '') {
                    error_class = '.error-password';
                }
                var err = $( error_class, this ).html()
                var show = true;
                // Look to see if the same error is already in the obj.errors array
                for (var i=0; i<obj.errors.length; i++) {
                    if (err == obj.errors[i] || err == null) {
                        show = false;
                    }
                }

                // If this is a unique error, add it to obj.errors
                if (show == true) {
                    obj.errors[obj.errors.length] = err;
                }

                // Add these groupErrors to the obj.hasErrors counter
                obj.hasErrors += obj.groupErrors;
            }

        });

        // If the onsubmit function has been defined, run it before we send off the data
        if (obj.params.onsubmit) {
            obj.params.onsubmit(obj);
        }
        // If no errors have been found, post the form!
        if (obj.hasErrors == 0) {


            if (obj.params.onsubmit) {
    			var submit = obj.params.onsubmit( obj.data );
    		} else {
    			var submit = true;
    		}

    		if (obj.params.confirm && obj.confirmed == false) {
    		    obj.confirm();
    		} else {

    			if (submit == true && this.submitted == false) {

    			    $.ajax({
                        type: "POST",
                        impersonatable: true,
                        url: obj.action,
                        data: obj.data,
                        dataType: 'json',
                        success: function( req ) {
                            // Handle the response
                            obj.handleResponse( req, obj );
                            // This removes the "loading" animation from the save button
                            $('img.button-loading').removeClass('button-loading');
                            obj.submitting = false;
                        },
                        error: obj.callback
                    });
                 }
    		}

        // Else show the users what they did wrong!
        } else {

            if (!this.params.suppress_errors) {
                // This removes the "loading" animation from the save button
                $('img.button-loading').removeClass('button-loading');
                // Show the error box
                this.errorBox( this.errors );
            }
            this.submitting = false;
        }
        return false;
    }
};

// TODO: Ability to support multiple classes
/* This function validates each form element based on different required types.
   The following classes can be added to the .jq-form-el element
        req-not-empty    : the user must specify a value for this element *TODO: change all references of "req-NotEmpty" to "req-not-empty"
        req-url         : the value must be a valid url
        req-int         : the value must be an integer
        req-email       : the value must be a valid email address
        req-choose-one  : the user must select at least one checkbox within the .jq-form-group element
        req-totals-100  : the value of all inputs must be greater than or equal to 0 and less than or equal to 100
   To allow multiple values within an element add the class "allow-multiple" to the jq-form-el element
        NOTE: multiple values MUST be delimited by a comma
   To validate a value only when something has been entered, add the class "if-not-empty"
*/
FormValidator.prototype.validate = function( el, elPar ) {

    // Add the value of the element to an array. We do this to support multiple values
    var value = [ el.value ];
    // Split the classname of the element to get each class
    var el_classes = el.className.split( ' ' );
    // Set the if_class var to false. This will be overwritten if "if-not-empty" is set
    var if_class = false;


    // Loop through each of the elements classes
    for (var i=0; i<el_classes.length; i++) {
        // Check to see if the class has a prefix of "req-". This is the validation type
        if (el_classes[i].indexOf('req-') == 0) {
            var type = el_classes[i].replace( 'req-', '' );
        }
        // Check to see if the class has a prefix of "if-". This is used for the if statement
        // Ie: only validate if not-empty
        if (el_classes[i].indexOf('if-') == 0) {
            if_class = el_classes[i].replace( 'if-', '' );
        }
        // If we're allowing multiple values that need to be validated, look for the class "allow-multiple"
        if (el_classes[i] == 'allow-multiple') {
            // Split the value on commas.
            value = el.value.split(',');

            // Trim the results (remove leading/trailing whitespace)
            for (v=0; v<value.length; v++) {
                value[v] = value[v].trim();
            }
        }
    }

    // We start at 0 errors.
    var hasErrors = 0;
    // Loop through each value and switch on the type
    for (var v=0; v<value.length; v++) {
        switch(type) {
            // Make sure they entered something.
            case 'not-empty':

                if (value[v].length == 0) {
                    hasErrors = 1;
                }

            break;
            // If we're looking for a name, make sure it has First and Last of at least 2 characters
            case 'name':
    			var name = value[v].split(' ');
    			var first = name[0];
    			var last = name[name.length-1];
    			if (!last || first.length < 2 || last.length < 2) {
    				hasErrors = 1;
    			}
    		break;
    		// Make sure they entered a 5 digit int
    		case 'zipcode':

    			var zip = value[v];
    			if (isNaN( parseInt(zip) ) || zip.length != 5) {
    				hasErrors = 1;
    			}

    		break;
    		// Validate they entered a phone number in the format XXX-YYY-ZZZZ
    		case 'phone':

    			var str = value[v];
    			var phone = /^1?-?\(?(\d{3})\)?[- ]?(\d{3})[- ]?(\d{4})$/;
    			if (phone.test(str) == false) {
    				hasErrors = 1;
    			} else {
    				str = str.replace( /\D/g, '' );
    				var strArr = str.split('');
    				strArr.splice(3,0,'-')
    				strArr.splice(7,0,'-')
    				value[v] = strArr.join('');
    			}

    		break;
    		case 'date':
    			var date = value[v];
    			for (key in el_classes) {
    				if (key.indexOf( 'date-') == 0) {
    					var date_type = key.replace( 'date-', '' );
    				}
    			}
    			var RegExPattern = /^(?=\d)(?:(?:(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[1,3-9]|1[0-2])(\/|-|\.)(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})|(?:0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))|(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:0?[1-9]|1\d|2[0-8])\4(?:(?:1[6-9]|[2-9]\d)?\d{2}))($|\ (?=\d)))?(((0?[1-9]|1[012])(:[0-5]\d){0,2}(\ [AP]M))|([01]\d|2[0-3])(:[0-5]\d){1,2})?$/;

    			if ((date.match(RegExPattern)) && (date != '')) {
    				val = date.replace( /\D/g, '-' );
    			} else {
    				hasErrors = 1;
    			}

    		break;
            // Perform a RegExp on the value to make sure it's a valid URL
            case 'url':

                var reg = /^(([\w]+:)?\/\/)?(([\d\w]|%[a-fA-f\d]{2,2})+(:([\d\w]|%[a-fA-f\d]{2,2})+))?([\d\w][-\d\w]{0,253}[\d\w]\.)+[\w]{2,4}(:[\d]+)?(\/([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)*(\?(&?([-+_~.\d\w]|%[a-fA-f\d]{2,2})=?)*)?(#([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)?$/;
                if(reg.test(value[v]) == false){
                    hasErrors = 1;
                }

            break;

            // Perform a RegExp to make sure the value is an integer. Perhaps I should use isNaN() here?
            case 'int':

                var reg = /^(\d{0,9})$/;
                if(reg.test(value[v]) == false || value[v].length == ''){
                    hasErrors = 1;
                }

            break;

            // Perform a RegExp to make sure the value is a valid email address
            case 'email':

                var reg = /^([a-zA-Z0-9_.-])+@(([a-zA-Z0-9-])+.)+([a-zA-Z0-9]{2,4})+$/;
                if (reg.test(value[v]) == false) {
                    hasErrors = 1;
                }

            break;

            // Validate that the same value is present in the password/confirm password fields
            case 'password':
                // Add support for password validation
                var confirm_pass = $('.req-confirm-password', this.form).val();
                if (confirm_pass != value[v] || value[v].length == 0) {
                    hasErrors = 1;
                }
            break;

            case 'confirm-password':
                if (value[v].length == 0) {
                    hasErrors = 1;
                }
            break;

            // Check to make sure the user has checked at least one checkbox
            case 'choose-one':

                var numCheck = 0;
                // Search the parent group for checkboxes
                var checks = $( elPar ).find( 'input[type=checkbox]' );
                for (var i=0; i<checks.length; i++) {
                    // If it's checked, great!
                    if (checks[i].checked == true) {
                        numCheck += 1;
                    }
                }
                // If none are checked, throw the error!
                if (numCheck == 0) {
                    hasErrors = 1;
                }
            break;

            // Validate that the value of the inputs are >= 0 && <= 100
            case 'totals-100':

                var total = 0;
                // Search the parent group for the inputs and loop through them
                var inputs = $( elPar ).find( 'input[type=text]' );
                for (var i=0; i<inputs.length; i++) {
                    var val = inputs[i].value;
                    // If the value is not a number, we have a problem
                    if (isNaN(val)) {
                        hasErrors = 1;
                        inputs[i].value = '';
                    // Else, add the value to the total
                    } else {
                        if (val == '') { val = 0; }
                        if (val < 0) {
                            val = 0;
                            inputs[i].value = '';
                        }
                        total += parseFloat( val );
                    }
                }
                // If the total is greater than 100 or less than 0, give 'em hell!
                if (total > 100 || total < 0) {
                    hasErrors = 1;
                }

            break;
        }

    }

    el.value = value.join(' ');

    // If the element has a class of "if-not-empty" and there is no value, don't show an error
    if ( if_class == 'not-empty' && el.value.length == 0 ) {
       hasErrors = 0;
    }

    // If no errors, we're good to go! Let's stuff the value into the data object
    if (hasErrors == 0 ) {
        // Remove the error class
        $( elPar ).removeClass( 'jq-form-has-error');
        // If the element is a checkbox or radio, and that element is checked, add the value to the data object
        if (el.type == 'checkbox' || el.type == 'radio' ) {
            if (el.checked == true)    {
                var data = el.value;
            }
        } else {
            var data = el.value;
        }
        // If we're not ignoring this value, add it to this.data
        if ( data && !$( el ).hasClass( 'ignore') ) {
            this.data[el.name] = data;
        }
    // Else remove the error class
    } else {
        $( elPar ).addClass( 'jq-form-has-error');
    }

    // Add the error count to the groupErrors counter
    this.groupErrors += hasErrors;

};

FormValidator.prototype.confirm = function( ) {
	var self = this;
	var title = self.params.confirm.title;
	var desc = self.params.confirm.desc;

	var text = desc+'<br><br>';
	text += '<table>'
	for (key in self.data) {

	    if ( !$('[name='+key+']', self.form).hasClass('jq-form-el-hidden')) {
			var data = self.data[key];
			if ($('select[name='+key+']', self.form)[0]) {
			    var data = $('select[name='+key+'] option:selected', self.form).html();
			}
			var id = $('[name='+key+']', self.form).attr('id');
			text += '<tr><td>';
			$('[name='+key+'] option', self.form).each( function() {
				if (this.selected == true) {
					data = this.innerHTML;
				}
			});
			var label = $('#' + id + '_label').html();
		    if (label.indexOf(':') == label.length-1) {
			    label = label.replace(':','');
			}
			text += label + ': </td><td>' + data;
			text += '</td></tr>';
		}
	}

	text += '</table>';
	this.submitting = false;

	$('#confirm_box_no_button').unbind().click( function() {
	   $('#confirm_box').dialog('close');
	   self.confirmed = false;
	});

	Dialogs.confirmBox(title, text, function(){
	    self.confirmed = true;
		self.formSubmit();
	});

};

// Handle the ajax response
FormValidator.prototype.handleResponse = function( req, obj ) {
    if (req) {
        if(req.errors) {
            // If we got errors back from the server, display them. This is the secondary validation layer
            obj.errorBox( req.errors );
        } else if (req.success) {
            // Else run the callback function
            obj.callback( req );
        }
    }
};

// Show the errors in a ui-dialog box
FormValidator.prototype.errorBox = function( errors ) {
    Dialogs.errorBox( errors );
};

// This is the default callback. You're really better off defining your own
FormValidator.prototype.defaultCallback = function( req, obj ) {
    if (req) {
        // If we got errors back from the server, display them.
        if(req.errors) {
            obj.errorBox( req.errors );
        // Else, tell them they did a good job.
        } else if (req.success) {
            alertBox( 'Your form has been submitted.' );
        }
    } else {
        // If there is no response object, give a generic error message
        errorBox( [ 'There was a problem with your post. Please try again.' ]);
    }
};


})(jQuery);


