I spent most of Sunday afternoon fixing a contact form on a client site instead of doing literally anything else I was supposed to be doing, and figured I'd write up what I ended up with. Nothing fancy here — no framework, no plugin, just plain jQuery 1.7 (which came out a couple weeks back, go grab it if you're still on 1.6.x) and a bit of patience.
The problem with most contact forms is they either don't validate at all, so you get emails that just say "asdf" in the message field, or they validate with some giant plugin that pulls in half a megabyte of JS to check if an email field is empty. I wanted something small I could actually read six months from now and understand.
The markup
Pretty boring stuff:
<form id="contact-form">
<input type="text" id="name" placeholder="Your name">
<input type="text" id="email" placeholder="Your email">
<textarea id="message" placeholder="Message"></textarea>
<button type="submit">Send it</button>
<div id="form-errors"></div>
</form>
Hooking the submit event
One nice thing 1.7 gave us is .on(), which is basically replacing .bind(), .live(), and .delegate() all at once. I've been using .live() for a couple years out of habit and it's deprecated now, so this seemed like a good excuse to finally switch over.
$(document).ready(function() {
$('#contact-form').on('submit', function(e) {
e.preventDefault();
validateAndSend();
});
});
e.preventDefault() is the whole trick here. Stop the browser from doing its normal form submission, run your own checks, and only fire the AJAX request if everything passes.
The actual validation
function validateAndSend() {
var name = $.trim($('#name').val());
var email = $.trim($('#email').val());
var message = $.trim($('#message').val());
var errors = [];
if (name === '') {
errors.push('You forgot your name.');
}
var emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(email)) {
errors.push('That email address doesn\'t look right.');
}
if (message.length < 10) {
errors.push('Message is too short. Give me something to work with.');
}
$('#form-errors').empty();
if (errors.length > 0) {
var list = $('<ul></ul>');
$.each(errors, function(i, err) {
list.append('<li>' + err + '</li>');
});
$('#form-errors').append(list);
return;
}
sendForm(name, email, message);
}
That email regex is not going to catch every technically-invalid address in existence — real email validation is a much bigger rabbit hole than it has any right to be, but it catches "bob" and "bob@" and "bob at gmail dot com," which covers 95% of what you actually run into on a form like this.
Sending it
function sendForm(name, email, message) {
$('#contact-form button').prop('disabled', true).text('Sending...');
$.ajax({
url: '/contact-handler.php',
type: 'POST',
data: { name: name, email: email, message: message },
success: function(response) {
$('#contact-form').html('<p>Thanks, got it.</p>');
},
error: function() {
$('#form-errors').text('Something broke on our end. Try again in a minute.');
$('#contact-form button').prop('disabled', false).text('Send it');
}
});
}
I disable the button while the request is in flight because I got tired of people double-clicking submit and generating two emails. Small thing, saves you some annoyance later.
A couple of notes if you're copying this: $.trim() is worth using on every field before you check length, because a textarea full of spaces will pass a naive .length check and it shouldn't. Also, don't skip server-side validation just because you did this client-side. Anybody with Firebug open can bypass all of this in about ten seconds, so the PHP on the other end needs to check everything again anyway. Client-side validation is for your actual users typing too fast, not for security.
I've been putting off writing about the Kindle Fire since it came out last weekend because it feels like every single tech blog on earth already has a review up, and honestly I haven't spent enough time with mine yet to say anything past "the browser is kind of sluggish." Maybe next week once I've actually used it on a real commute instead of just poking at it on the couch.
Anyway. Total JS for this whole thing is under 40 lines and it works in everything back to IE7 as far as I've tested. That's the whole post.