Introduction
In this tip I’m going to simplify a process I find myself
doing over and over by using a jQuery Extension method.
To begin let’s review a block of code I found my self
writing over and over in various parts of my web applications.
The Hard Way
<script type="text/javascript">
$(function () {
$("#myForm").submit(function (e) {
e.preventDefault();
var postData = $("#myForm").serialize();
var action = $("#myForm").attr("action");
$.post(action, function (data) {
$("#PostResult").html(data);
});
});
});
</script>
<form id="myForm" action="someservice.ashx">
<input type="text" name="Field1" /><br />
<input type="text" name="Field2" /><br />
<input type="text" name="Field3" /><br />
<input type="submit"/>
</form>
<div id="PostResult"></div>
Essentially I’m
taking a form and rather than doing a traditional POST, I’m serializing
the form and posting it using the JQuery $.Post() function.
This works fine – but having to do this every time you want to AJAXify a form is pain.
The Better Way – Using a JQuery Extension
So let’s refractor this into an extension method so it’s reusable. To begin let’s create a bare extension method.
$.fn.ajaxform = function(options) {
return this.each(function () {
});
};
The $.fn.ajaxform is the name of your method so now we will be able to call it like so : $(“#myForm”).ajaxform();
if
you don’t follow the return this.each pattern (which is not required)
your method will not be chain-able. That means it will always have to
come at the end of the chain. So if you want that then omit that
section, however it’s highly recommended to maintain the chain.
Next were going to build up our option stack and set some default values.
$.fn.ajaxform = function (options) {
var settings = $.extend({
'postData': $(this).serialize(),
'action': $(this).attr("action"),
'onComplete': function (data) {}
}, options);
return this.each(function () {
});
};
see how we just brought our variables we used up above and made them
into options for method. Now when using our method you can either use
the default functionality – or override as needed.
Now lets add our functionality.
$.fn.ajaxform = function (options) {
var settings = $.extend({
'postData': $(this).serialize(),
'action': $(this).attr("action"),
'onComplete': function (data) {}
}, options);
return this.each(function () {
$(this).submit(function (e) {
e.preventDefault();
$.post(settings.action, settings.postData, settings.onComplete);
});
});
};
And now we can replace our original script to use our method like this:
$(function () {
$("#myForm").ajaxform({
onComplete: function (data) {
$("#PostResult").html(data);
}
});
});
That’s much easier on the eyes considering we will probably be using it a lot.
We have now completed the refactor of the original code, but to
complete the AJAXForm extension I want to add the following
functionality:
- Ability to check for required fields,
- Mark these fields as required
- Display an error if not completed, and of course not submit if omitted.
Required Fields
Ok first lets do the required fields.
To do this were going to
add in a setting option to pass in a string array with the names of the
required fields. Create a function to perform the validation, and call
this function prior to submitting our form.
$.fn.ajaxform = function (options) {
var settings = $.extend({
'requiredFields': [],
'errorMessageTarget': '',
...
}, options);
var checkRequiredFields = function() {
return {
isValid: false,
errorMessage: ''
};
};
return this.each(function () {
$(this).submit(function (e) {
e.preventDefault();
var validationResult = checkRequiredFields();
if (validationResult.isValid) {
$.post(settings.action, settings.postData, settings.onComplete);
}else {
$(settings.errorMessageTarget).html(validationResult.errorMessage);
}
});
});
};
And now we need to write the code to check to ensure the form values are not
null.
var checkRequiredFields = function(form) {
var isValid = true;
var errors = "";
for (var i in settings.requiredFields) {
var rField = $(form).find(settings.requiredFields[i]);
if ($(rField).val() == "") {
errors += "<li>" + $(rField).attr("name") + " is Required</li>";
$(rField).addClass(settings.fieldErrorClass);
isValid = false;
} else {
$(rField).removeClass(settings.fieldErrorClass);
}
}
return {
isValid: isValid,
errorMessage: errors
};
};
Next we want to add a required field class to each of the required form fields.
to do this were going to add a little code into the return this.each(function () block
return this.each(function () {
for (var i = 0; i < settings.requiredFields.length; i++) {
$(this).find('*[name="' + settings.requiredFields[i] + '"]').addClass(settings.requiredFieldClass);
}
....
OK,so that completes our required field validation – here’s a post of the entire
code to this point:
<script type="text/javascript">
$(function () {
$("#myForm").ajaxform({
requiredFields: ["Field1", "Field2"],
errorMessageTarget : "#PostResult",
onComplete: function (data) {
$("#PostResult").html(data);
}
});
});
</script>
<form id="myForm" action="someservice.ashx">
<input type="text" name="Field1" /><br />
<input type="text" name="Field2" /><br />
<input type="text" name="Field3" /><br />
<input type="submit"/>
</form>
<div id="PostResult"></div>
<script type="text/javascript">
$.fn.ajaxform = function (options) {
var settings = $.extend({
"requiredFields": [],
"requiredFieldClass" : "field-required",
"errorMessageTarget": "",
"fieldErrorClass": "field-error",
"postData": $(this).serialize(),
"action": $(this).attr("action"),
"onComplete": function (data) {}
}, options);
var checkRequiredFields = function(form) {
var isValid = true;
var errors = "";
for (var i in settings.requiredFields) {
var rField = $(form).find(settings.requiredFields[i]);
if ($(rField).val() == "") {
errors += "<li>" + $(rField).attr("name") + " is Required</li>";
$(rField).addClass(settings.fieldErrorClass);
isValid = false;
} else {
$(rField).removeClass(settings.fieldErrorClass);
}
}
return {
isValid: isValid,
errorMessage: errors
};
};
return this.each(function () {
for (var i = 0; i < settings.requiredFields.length; i++) {
$(this).find('*[name="' + settings.requiredFields[i] + '"]').addClass(settings.requiredFieldClass);
}
$(this).submit(function (e) {
e.preventDefault();
var validationResult = checkRequiredFields(this);
if (validationResult.isValid) {
$.post(settings.action, settings.postData, settings.onComplete);
}else {
$(settings.errorMessageTarget).html(validationResult.errorMessage);
}
});
});
};
</script>
Before Submit Function
Our next Item on the agenda is to : Provide the ability to pass in a
function that executes before the form is submitted (for additional
validation). So that means we need a method that has the ability to
append to our validation result. This means were going to need to
extract the validation result into a variable and work against it in our
new method.
var validationState = {
isValid: true,
errorMessage: ''
};
var checkRequiredFields = function(form) {
for (var i in settings.requiredFields) {
var rField = $(form).find('*[name="' + settings.requiredFields[i] + '"]');
if ($(rField).val() == "") {
validationState.errorMessage +=
"<li>" + $(rField).attr("name") + " is Required</li>";
$(rField).addClass(settings.fieldErrorClass);
validationState.isValid = false;
} else {
$(rField).removeClass(settings.fieldErrorClass);
}
}
};
Now were going to add a method that takes in the validationState and is run before the required filed check
var settings = $.extend({
....
'beforeSubmit': function(valState) { },
.....
}, options);
...
$(this).submit(function (e) {
e.preventDefault();
settings.beforeSubmit(validationState);
checkRequiredFields(this);
....
And now we can leverage it in our initialization code like this:
$("#myForm").ajaxform({
requiredFields: ['Field1', 'Field2'],
beforeSubmit: function (validationState) {
if ($('input[name="Field3"]').val() != "foobar") {
validationState.isValid = false;
validationState.errorMessage += '<li>Field3 Needs to be FooBar</li>';
}
},
errorMessageTarget: '#PostResult',
onComplete: function (data) {
$("#PostResult").html(data);
}
});
});
And with that I Believe we have completed our requirements.
Final Code
<script type="text/javascript">
$(function () {
$("#myForm").ajaxform({
requiredFields: ["Field1", "Field2"],
beforeSubmit: function (validationState) {
if ($('input[name="Field3"]').val() != "foobar") {
validationState.isValid = false;
validationState.errorMessage += '<li>Field3 Needs to be FooBar</li>';
}
},
errorMessageTarget: '#PostResult',
onComplete: function (data) {
$("#PostResult").html(data);
}
});
});
</script>
<form id="myForm" action="someservice.ashx">
<input type="text" name="Field1" /><br />
<input type="text" name="Field2" /><br />
<input type="text" name="Field3" /><br />
<input type="submit"/>
</form>
<div id="PostResult"></div>
<script type="text/javascript">
$.fn.ajaxform = function (options) {
var settings = $.extend({
"requiredFields": [],
"requiredFieldClass" : "field-required",
"errorMessageTarget": "",
"fieldErrorClass": "field-error",
"beforeSubmit": function(valState) { },
"postData": $(this).serialize(),
"action": $(this).attr("action"),
"onComplete": function(data) { }
}, options);
var validationState = {
isValid: true,
errorMessage: ''
};
var checkRequiredFields = function(form) {
for (var i in settings.requiredFields) {
var rField = $(form).find('*[name="' + settings.requiredFields[i] + '"]');
if ($(rField).val() == "") {
validationState.errorMessage += "<li>" + $(rField).attr("name") + " is Required</li>";
$(rField).addClass(settings.fieldErrorClass);
validationState.isValid = false;
} else {
$(rField).removeClass(settings.fieldErrorClass);
}
}
};
return this.each(function () {
for (var i = 0; i < settings.requiredFields.length; i++) {
$(this).find('*[name="' + settings.requiredFields[i] + '"]').addClass(settings.requiredFieldClass);
}
$(this).submit(function (e) {
e.preventDefault();
settings.beforeSubmit(validationState);
checkRequiredFields(this);
if (validationState.isValid) {
$.post(settings.action, settings.postData, settings.onComplete);
}else {
$(settings.errorMessageTarget).html(validationState.errorMessage);
}
});
});
};
</script>
If you found this article helpfull you can find more like it at http://www.sympletech.com
This code is also part of the SympleLibJS Components found here: https://github.com/sympletech/SympleLib.