Building a Form Validator

Early last year (2017) we had just finished a Sitecore implementation that included several different contact forms, a look-up form, and a product order form. Unfortunately each form was created by a different developer and each form took a different approach to achieve form validation and provide user feedback. That’s a problem all by itself but it’s also a problem that’ll grow more severe as more and more people work on the existing forms or add new forms to the site.

The first step to correcting an issue like this is to standardize your form validation. So I needed to build something that would satisfy all of our requirements, be reasonably extensible, and bring together all of the various validation approaches everyone took into one reusable class. That’s where FormValidator was born.

But wait! Why would you build your own validator when there are already libraries that do this? It’s simply down to the initial load time. We already use so many different libraries on our site that it’s becoming noticeably slower than some of our competitors, and adding yet another bloated “works in all scenarios” library to our ever-growing collection would only add to the problem. Keep in mind that load times are taken into consideration when ranking your site by major search engines like Google. We’re already using jQuery, and the FormValidator takes advantage that fact for all its DOM manipulations.

FormValidator is a Javascript class that accepts an object (or an array of objects) as an input. The object that gets passed to FormValidator must contain a list of all of the fields you need to validate along with supporting properties related to each field such as minimum and maximum allowed values, error message copy, auto-formatting options, etc. FormValidator itself is an object that is structured as a class with various methods. Some methods can be called independently if you wish, but for the most part, you only need to create an instance of the FormValidator object, provide it a reference to your field list object, and then act on the boolean value it returns after validating your form. That is, you should prevent the default behavior of your form submission, and only actually submit the form if the FormValidator returns true.

The first step is to create the “fields object”. The fields object will contain all of the fields you want validated in your form. The fields object can be rendered in a script tag in your view/HTML page to allow for setting the messages and parameters programmatically by your controller and then just pass that object to your FormValidator instance. Keeping the name spacing the same between the two in this case can make things easier and cleaner. This was the approach we took in our Sitecore instance because the error messages were authorable, so the Javascript field list object needed to be contained in the view.

Pass the FormValidator’s validate method two things: 1) an object containing name/property pairs with the name/indexer containing the element ID of the form field and then a set of properties as described below and 2) a parameter value of true or false to tell the FormValidator whether or not to display all error messages related to a field (error stacking) or display them one at a time (only display the first encountered error message when validating a field):

  • fieldType: This can be any of the following: extendedalphanumeric, alphanumeric, int, float, alpha, percentage, email, phone, zipcode, date, ssn, checkbox (individual radio buttons are evaluated as checkboxes), select, radiogroup (a group of radio buttons with unique IDs and a common name; use the common name instead of the id in this case; highlight class is applied to the parent container (that has a class of “.form-validator-group”) of the named elements)
  • minLength and maxLength: For extendedalphanumeric, alphanumeric, and alpha this optional property will evaluate the length of the string.
  • minValue and maxValue: For the int and float types this optional property will evaluated the range of the field’s data as if is a number using less-than and greater-than.
  • compareField: The field ID to compare this field against. If the field value being validated doesn’t match the compareField’s value, the compareFieldError is displayed and this field is invalidated/returns false.
  • fieldIsRequired: if set to true the field must contain data or be selected. Can be set to false or omitted if the field is not required.
  • errorType: The message to display when the input contains characters that do not match the specified type.
  • errorRange: This is the out of range error which is displayed based on fieldMin and fieldMax.
  • compareFieldError: The error message to display when the compareField ID’s value doesn’t match the value of the field being validated.
  • errorRequired: The error to display if the field is empty or unselected when it is required.
  • errorInsertAfter: This is a reference to the element ID you’d like FormValidator to highlight and insert the error message element under/after. Leaving this property blank or omitting it causes the script to use the field list object key name (which should be the ID of each of the form’s inputs) It is assumed that the input element/insertAfterId element ID will be a container for an element or set of elements and the error text element will be inserted as a sibling of this container. The terms of service checkbox in this example uses this feature to put a border around the checkbox and the checkbox description text on error. Other examples of this being used would be for a set of radio buttons that have a container div around them.
  • enforceType: If this property is included and set to true, keypresses that do not match the characters for a particular fieldType will be prevented. This works on all valid fieldType settings that are not set to checkbox or select and is activated by calling the runTypeEnforcerOnEntry method and passing it your form fields object.
  • autoFormat: This property can be set to true or false and works when enforceType is also set to true and is activated. It can apply automatic formatting to the following field types: currency, percentage, phone, and ssn.

Example Field List Object

var fields = [{
	"first-name": {
		fieldType: "extendedalphanumeric",
		minLength: 2,
		maxLength: 20,
		fieldIsRequired: true,
		errorType: "The first name you entered contains invalid characters.",
		errorRange: "First names must be between 2 and 20 characters.",
		errorRequired: "Please provide your first name.",
		errorInsertAfter: "",
		enforceType: true
	},
	"last-name": {
		fieldType: "alphanumeric",
		maxLength: 38,
		fieldIsRequired: false,
		errorType: "The last name you entered contains invalid characters.",
		errorRange: "Last names cannot exceed 38 characters.",
		errorRequired: "Please provide your last name.",
		enforceType: false
	},
	"age": {
		fieldType: "float",
		minValue: 16,
		maxValue: 125,
		fieldIsRequired: true,
		errorType: "The age you entered contains invalid characters.",
		errorRange: "Age must be between 16 and 125.",
		errorRequired: "Please enter your age.",
		enforceType: true
	},
	"date-of-birth": {
		fieldType: "date",
		fieldIsRequired: true,
		errorType: "The birth date you provided is not valid.",
		errorRequired: "Please enter your date of birth.",
		enforceType: true
	},
	"phone": {
		fieldType: "phone",
		fieldIsRequired: true,
		errorType: "The phone number you provided is not valid.",
		errorRequired: "Please enter your phone number.",
		insertErrorAfter: "",
		enforceType: true,
		autoFormat: true
	},
	"email-address": {
		fieldType: "email",
		errorType: "Please enter a valid e-mail address.",
		errorRequired: "Please provide an e-mail address.",
		enforceType: true
	},
	"state": {
		fieldType: "select",
		fieldIsRequired: true,
		errorRequired: "Please select a state."
	},
	"zip-code": {
		fieldType: "zipcode",
		fieldIsRequired: true,
		errorType: "The zip code you provided is not valid.",
		errorRequired: "Please enter a zip code.",
		enforceType: true
	},
	"agree": {
		fieldType: "checkbox",
		fieldIsRequired: true,
		errorRequired: "You must agree to our terms of service.",
		errorInsertAfter: "agree-text"
	}
}];

Complete FormValidator Class

//A set of form validator methods to validate field data prior to posting the form to a controller:
var FormValidator = {
    fieldTypes: ["extendedalphanumeric", "currency", "email", "phone", "zipcode", "date", "percentage", "ssn", "int", "float", "alphanumeric", "alpha"],
 
    validate: function (fieldList, errorStacking) {
        this.removeAllInputErrors(); //Clear any existing error elements on the form.
 
        if (typeof errorStacking === 'undefined') errorStacking = true;
        var formStatus = true; //Default for formStatus is true, but it can be changed to false by the validation logic below:
        var fieldToValidate = "";
        var insertAfterId = "";
        var fieldErrors = [];
 
        //Cycle through the fieldList object.
        for (var key in fieldList) {
            var fieldStatus = true;
            fieldToValidate = $("#" + key);
            var obj = fieldList[key];
 
            //If errorInsertAfter is supplied and not empty set it to the errorInsertAfter ID and add the #, otherwise just use the fieldToValidate variable.
            insertAfterId = (typeof obj.errorInsertAfter !== 'undefined' && obj.errorInsertAfter != "")
                                                                ? "#" + obj.errorInsertAfter
                                                                : fieldToValidate;
 
            //Step 1: Check if the field is required. If it is, it cannot be empty or unchecked. (all fields get an empty check, but this is also where checkboxes, radio buttons, and selects get handled)
            if (typeof obj.fieldIsRequired !== 'undefined' && obj.fieldIsRequired) {
                if ((obj.fieldType == "checkbox" && !this.isChecked(fieldToValidate)) || (obj.fieldType == "radiogroup" && !this.isCheckedRadioGroup(key)) || this.isEmpty(fieldToValidate)) {
                    fieldStatus = false;
                    fieldErrors[fieldErrors.length] = obj.errorRequired;
                    if (obj.fieldType == "radiogroup") {
                        insertAfterId = $("input[name='" + key + "']").parent().closest(".form-validator-group");
                    }
                }
            }
 
            //The validationMethods array must match the length of the fieldTypes array! The order is important!
            var validationMethods = [this.isExtendedAlphaNumeric, this.isCurrency, this.isEmail, this.isPhone, this.isZipCode, this.isDate, this.isPercentage, this.isSSN, this.isInt, this.isFloat, this.isAlphaNumeric, this.isAlpha];
 
            //Step 2: Verify input complies with field type
            for (var i = 0; i < this.fieldTypes.length; i++) {
                if (obj.fieldType == this.fieldTypes[i]) {
                    if (!this.isEmpty(fieldToValidate) && !validationMethods[i](fieldToValidate)) {
                        fieldStatus = false;
                        fieldErrors[fieldErrors.length] = obj.errorType;
                        break; //Once we have what we need, we leave -- so cold.
                    }
                }
            }
 
            //Step 3. Check min/max if there's something in the input field. (currency and percentage types are stripped to just floating point so they can be compared as numerical values. if there's nothing in the input and it's required, the required check will have caught it)
            if ((obj.fieldType == "currency" || obj.fieldType == "percentage") && !this.isEmpty(fieldToValidate)) {
                if ((typeof obj.maxValue !== 'undefined' && obj.maxValue != "" && $(fieldToValidate).val().replace(/,|\$|%/gi, "") > obj.maxValue) ||
                                                                                                (typeof obj.minValue !== 'undefined' && obj.minValue != "" && $(fieldToValidate).val().replace(/,|\$|%/gi, "") < obj.minValue)) {
                    fieldStatus = false;
                    fieldErrors[fieldErrors.length] = obj.errorRange;
                }
            } else if ((obj.fieldType == "int" || obj.fieldType == "float") && !this.isEmpty(fieldToValidate)) {
                if ((typeof obj.maxValue !== 'undefined' && obj.maxValue != "" && $(fieldToValidate).val() > obj.maxValue) ||
                                                                                                (typeof obj.minValue !== 'undefined' && obj.minValue != "" && $(fieldToValidate).val() < obj.minValue)) {
                    fieldStatus = false;
                    fieldErrors[fieldErrors.length] = obj.errorRange;
                }
            } else if ((obj.fieldType == "extendedalphanumeric" || obj.fieldType == "alphanumeric" || obj.fieldType == "alpha") && !this.isEmpty(fieldToValidate)) {
                if ((typeof obj.maxLength !== 'undefined' && obj.maxLength != "" && $(fieldToValidate).val().length > obj.maxLength) ||
                                                                                                (typeof obj.minLength !== 'undefined' && obj.minLength != "" && $(fieldToValidate).val().length < obj.minLength)) {
                    fieldStatus = false;
                    fieldErrors[fieldErrors.length] = obj.errorRange;
                }
            }
 
            //Step 4. Check for comparison field verification (for things like the second e-mail address verification field "Please retype your e-mail address below."):
            if (!this.isEmpty(fieldToValidate)) {
                if ((typeof obj.compareField !== 'undefined' && obj.compareField != "") && ($(fieldToValidate).val() != $("#" + obj.compareField).val())) {
                    fieldStatus = false;
                    fieldErrors[fieldErrors.length] = obj.compareFieldError;
                }
            }
 
            //Step 5. Display the error message for this field.
            if (!fieldStatus) {
                this.displayInputError(fieldErrors, errorStacking, insertAfterId);
                fieldErrors = [];
            }
 
            //Setup the return status for this form. Default status is true. (formStatus = fieldStatus if fieldStatus != true)
            formStatus &= fieldStatus;
        }
 
        //If there are valiadtion errors, auto-scroll to the first error:
        if (!formStatus) {
            //handle a "back to top" element if needed (shows up in mobile at the top when a user starts swiping down on the page to scroll it towards the bottom)
            var backToTopOffset = 0;
            if ($(".back-to-top").css("display") != "none") {
                backToTopOffset = $(".back-to-top").height();
            }
 
            var scrollTo = $("body .form-validator-highlight").first();
            $("html, body").animate({ scrollTop: scrollTo.offset().top - backToTopOffset}, 300);
        }
 
        //Return the overall status for all the form elements that were evaluated (if any fields that were evaluated returned false then this will also return false):
        return (formStatus);
    },
 
    isExtendedAlphaNumeric: function (id) {
        return /^[A-Za-z0-9!@#$%&*()\-_=+:;'",.?\s]*$/.test($(id).val());
    },
 
    isCurrency: function (id) {
        return /^\$[\d,]*\.{1}[\d]{2}$/.test($(id).val());
    },
 
    isEmail: function (id) {
        return /^(([A-Za-z0-9_])+([.\-+]+([A-Za-z0-9_])+)*@([A-Za-z0-9])+([.\-]?([A-Za-z0-9])+)*(\.[A-Za-z0-9]{2,3})+)$/.test($(id).val());
    },
 
    isPhone: function (id) {
        return /^(\(?([0-9]{3})\)?[-. ]?([0-9]{3}) [-. ]? ([0-9]{4}))*$/.test($(id).val());
    },
 
    isZipCode: function (id) {
        return /^(([0-9]{5})|(([0-9]{5})\-([0-9]{4})))$/.test($(id).val());
    },
 
    isDate: function (id) {
        if ($(id).prop("type") == "date") {
            var test = $(id).val().replace(/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/i, "$2-$3-$1");
        } else {
            test = $(id).val();
        }
       
        return /^(((0[1-9]|1[0-2])[-. /]?)((([0][1-9])|([1-2][0-9])|(3[0-1]))[-. /]?)([0-9]{4}))$/i.test(test);
    },
 
    isPercentage: function (id) {
        return /^[\d]*\.*[\d]*%$/.test($(id).val());
    },
 
    isSSN: function (id) {
        return /^(([0-9]{3})[- ]?([0-9]{2})[- ]?([0-9]{4}))$/.test($(id).val());
    },
 
    isInt: function (id) {
        return /^[\d]*$/.test($(id).val());
    },
 
    isFloat: function (id) {
        return /^[\d]*\.*[\d]*$/.test($(id).val());
    },
 
    isAlphaNumeric: function (id) {
        return /^[A-Za-z\d.\s]*$/.test($(id).val());
    },
 
    isAlpha: function (id) {
        return /^[A-Za-z\s]*$/.test($(id).val());
    },
 
    isCheckedRadioGroup: function (id) {
        if ($("input[name='" + id + "']:checked").val()) {
            return true;
        } else {
            return false;
        }
    },
 
    isChecked: function (id) {
        if (($(id).is(":radio") || $(id).is(":checkbox")) && $(id).prop('checked')) {
            return true;
        } else {
            return false;
        }
    },
 
    isEmpty: function (id) {
        return ($(id).is("input:text") || $(id).is("input[type=email]") || $(id).is("input[type=tel]") || $(id).is("input[type=password]") || $(id).is("textarea") || $(id).is("select") || $(id).is("input[type=date]")) && $.trim($(id).val()).length < 1;
    },
 
    //This method runs the keypress/realtime entry checking against all the elements in the object you pass to it:
    runTypeEnforcerOnEntry: function (fieldList) {
        for (var key in fieldList) {
            this.fieldToValidate = $("#" + key);
            var obj = fieldList[key];
 
            if (typeof obj.enforceType !== 'undefined' && obj.fieldType != "checkbox" && obj.fieldType != "radiogroup" && obj.fieldType != "select" && obj.enforceType) {
                $(this.fieldToValidate).keypress(this.generateEnforceTypeBinding(obj.fieldType));
            }
 
            //auto-format
            if (typeof obj.autoFormat !== 'undefined' && (obj.fieldType == "currency" || obj.fieldType == "phone" || obj.fieldType == "percentage" || obj.fieldType == "ssn") && obj.autoFormat) {
                this.autoFormat(this.fieldToValidate, obj.fieldType);
            }
        }
    },
 
    //This method is only used for the keypress/input checking:
    generateEnforceTypeBinding: function (fieldType) {
        return function (event) {
            MyWebSite.Web.Common.FormValidator.enforceType(event, fieldType);
        };
    },
 
    //This method is only used for the keypress/input checking.
    enforceType: function (inputEvent, fieldType) {
        if (fieldType != "checkbox" && fieldType != "radiogroup" && fieldType != "select") {
            var event = inputEvent || window.event;
            var keyPress = event.keyCode || event.which;
 
            //The length of the rxPatterns array must match the length of fieldTypes array! The order is important!
            var rxPatternsValidChars = [/[A-Za-z0-9!@#$%&*()\-_=+:;'",.?\s\b\t]/, /[0-9.,$\b\t]/, /[\-_+.@\b\t]|\w/, /[0-9 \-.()\b\t]/, /[0-9\-\b\t]/, /[0-9 \-/.\b\t]/, /[0-9.%\b\t]/, /[0-9 \-\b\t]/, /[\d\b\t]/, /[\d.\b\t]/, /[A-Za-z\d.\s\b\t]/, /[A-Za-z\s\b\t]/];
            var regEx;
 
            for (var i = 0; i < this.fieldTypes.length; i++) {
                if (fieldType == this.fieldTypes[i]) {
                    regEx = rxPatternsValidChars[i];
                    var character = String.fromCharCode(keyPress);
 
                    if (!regEx.test(character)) {
                        if (inputEvent.preventDefault) {
                            inputEvent.preventDefault();
                            inputEvent.returnValue = false; //Deprecated; for backward compatibility only.
                            break; //Once we have what we need, we leave -- so cold.
                        }
                    }
                }
            }
        }
    },
 
    autoFormat: function (fieldToValidate, fieldType) {
        if (fieldType == "currency") {
            $(fieldToValidate).on('keyup', function (e) {
 
                var value = $(this).val();
                value = value.replace(/\$/g, '').replace(/\./g, '').replace(/,/g, '').replace(/^0+/, '');
                var charCode = String.fromCharCode(e.keyCode);
 
                   if (value.length == 2) value = '0' + value;
                   if (value.length == 1) value = '00' + value;
 
                   var formatted = '';
                   for (var i = 0; i < value.length; i++) {
                       var sep = '';
                       console.log(" i= " + i + " value.length= " + value.length);
                       if (i == 2) sep = '.';
                       if (i > 3 && (i + 1) % 3 == 0) sep = ',';
                       formatted = value.substring(value.length - 1 - i, value.length - i) + sep + formatted;
                   }
                   if (formatted.indexOf("$") > -1) {
                       $(this).val(formatted);
                       console.log(formatted);
                   } else {
                       $(this).val('$' + formatted);
                       console.log('$' + formatted);
                   }
            });
        }
 
        if (fieldType == "percentage") {
            $(fieldToValidate).keyup(function (inputEvent) {
                var event = inputEvent || window.event;
                var keyPress = event.keyCode || event.which;
                var character = String.fromCharCode(keyPress);
                var regEx = /[\b\t]/;
 
                if (!regEx.test(character)) {
                    var thisFieldValue = this.value.replace(/%/gi, "");
                    $(this).val(thisFieldValue + "%");
                }
            });
        }
 
        if (fieldType == "phone") {
            $(fieldToValidate).keyup(function (inputEvent) {
                var event = inputEvent || window.event;
                var keyPress = event.keyCode || event.which;
                var character = String.fromCharCode(keyPress);
                var regEx = /[\b\t]/;
 
                if (!regEx.test(character)) {
                    var thisFieldValue = this.value.replace(/\D/gi, "");
                    thisFieldValue = thisFieldValue.substr(0, 10);
 
                    var size = thisFieldValue.length;
 
                    if (size < 4) {
                        thisFieldValue = "(" + thisFieldValue;
                    } else if (size < 7) {
                        thisFieldValue = "(" + thisFieldValue.substring(0, 3) + ") " + thisFieldValue.substring(3, 6);
                    } else {
                        thisFieldValue = "(" + thisFieldValue.substring(0, 3) + ") " + thisFieldValue.substring(3, 6) + " - " + thisFieldValue.substring(6, 10);
                    }
 
                    $(this).val(thisFieldValue);
                }
            });
        }
 
        if (fieldType == "ssn") {
            $(fieldToValidate).keyup(function (inputEvent) {
                var event = inputEvent || window.event;
                var keyPress = event.keyCode || event.which;
                var character = String.fromCharCode(keyPress);
                var regEx = /[\b\t]/;
 
                if (!regEx.test(character)) {
                    var thisFieldValue = this.value.replace(/\D/gi, "");
                    thisFieldValue = thisFieldValue.substr(0, 9);
 
                    var size = thisFieldValue.length;
 
                    if (size > 3 && size < 6) {
                        thisFieldValue = thisFieldValue.substring(0, 3) + "-" + thisFieldValue.substring(3, 5);
                    } else if (size > 5) {
                        thisFieldValue = thisFieldValue.substring(0, 3) + "-" + thisFieldValue.substring(3, 5) + "-" + thisFieldValue.substring(5, 9);
                    }
 
                    $(this).val(thisFieldValue);
                }
            });
        }
    },
 
    highlightField: function (id) {
        $(id).addClass("form-validator-highlight");
        $("label[for='" + $(id).attr('id') + "']").addClass("form-validator-highlight");
    },
 
    displayInputError: function (fieldErrors, errorStacking, insertAfterId) {
                //if errorStacking was set to true display each error for the field otherwise just show the first error
			if (errorStacking) {
				var aErrorsCombined = "";
				$.each(fieldErrors, function (index, error) {
					aErrorsCombined += "<div class=\"form-validator-error\">" + error + "</div>";
				});

				$(aErrorsCombined).insertAfter(insertAfterId);
			} else {
				$("<div class=\"form-validator-error\">" + fieldErrors[0] + "</div>").insertAfter(insertAfterId);
			}
 
			MyWebSite.Web.Common.FormValidator.highlightField(insertAfterId);
    },
 
    removeAllInputErrors: function () {
        $(".form-validator-error").remove();
        $(".form-validator-highlight").removeClass("form-validator-highlight");
    }
};
 
//Namespacing
window.MyWebSite = window.MyWebSite || {};
window.MyWebSite.Web = window.MyWebSite.Web || {};
window.MyWebSite.Web.Common = window.MyWebSite.Web.Common || {};
window.MyWebSite.Web.Common.FormValidator = FormValidator;

Calling the FormValidator Object

$(document).ready(function () {
	
	//You can run the entry type enforcer method after your page loads to check if the user is entering characaters that match the field's assigned type. If the input characater don't match, the input is halted. You can use the enforeType property in your field list object to determine which fields will be affected by this method.
	FormValidator.runTypeEnforcerOnEntry(fields[0]);
	
	/*
		You could also enforce types during entry on a per-field basis by accessing the enforceType method by itself, like this:
		
		$("#last-name").keypress(function (event) {
			FormValidator.enforceType(event, "alpha");
		});
		
		But for inputs that you want realtime type enforcement applied to, it's easier to just include the "enforceType : true" property in your form fields object and then run the realtime type enforcer (runTypeEnforcerOnEntry) in addition to the validator. (see this example)
	*/
	
	//FormValidator.validate() expects an object that contains a list of fields (see the top of this script for details) and returns true or false in addition to displaying error messages when necessary.
	$("#test-form").submit(function (event) {
		event.preventDefault();
		
		if (FormValidator.validate(fields[0])) {
			alert("Your form entries look good! (This alert is just for demonstration purposes. Normally at this point you'd either post the data or move to the next section of the form.");
			//You could have an array of fields objects for a multi-section form and when this evaluation is true, you evaluate the next fields object index like this: if (FormValidator.validate(fields[1]))... and so on.
		}
		
		return false;
	});
});

CSS and HTML For Example FormValidator Call (Above)

<html>
<head>
<title>FormValidator Test</title>
<style>
body {
	font-family: Arial;
}

form div {
	vertical-align: middle;
}

form div label {
	vertical-align: top;
}

form > div > div {
	display: inline-block;
}

label {
	display: inline-block;
	width: 100px;
	font-size: 16px;
	padding: 5px;
	margin: 5px;
}

input[type=text], input[type=checkbox], select {
	display: inline-block;
	font-size: 16px;
	padding: 5px;
	border-radius: 5px;
	margin: 5px;
}

input[type=text] {
	width: 300px;
	border: 1px solid #999999;
}

#agree-text {
	padding: 5px;
	border-radius: 5px;
}

input[type=text].form-validator-highlight, .form-validator-highlight {
	margin-top: 5px;
	background: #ffeeee;
	border: 1px solid #ba0000;
}

.form-validator-error {
	display:block;
	margin: 0px 0px 5px 5px;
	color: #ba0000;
	font-size: 10px;
}

form div #agree-text.form-validator-highlight {
	margin: 5px 0px 5px 5px;
}
</style>
<script></script>
</head>
<body>
<form id="test-form">
	<div><label>First Name:</label><div><input type="text" id="first-name"></div></div>
	<div><label>Last Name:</label><div><input type="text" id="last-name"></div></div>
	<div><label>Age:</label><div><input type="text" id="age"></div></div>
	<div><label>Date of Birth:</label><div><input type="text" id="date-of-birth"> mm/dd/yyyy</div></div>
	<div><label>Phone:</label><div><input type="text" id="phone"></div></div>
	<div><label>E-mail:</label><div><input type="text" id="email-address"></div></div>
	<div><label>State:</label><div><select id="state"><option value="">Select a State</option><option value="California">California</option><option value="Minnesota">Minnesota</option><option value="North Dakota">North Dakota</option></select></div></div>
	<div><label>Zip Code:</label><div><input type="text" id="zip-code"></div></div>
	<div><label></label><div><div id="agree-text"><input type="checkbox" id="agree"> I agree to the <a href="#">terms of service.</a></div></div></div>
	<div><input type="submit" id="btnSubmit"></div>
</form>
</body>
</html>

Leave a Reply