« Simple browser and OS sniffing in ColdFusion | Main | Validating multiple emails in one field-- server-side »

Validating multiple emails in one field

I'm updating Webmail, a web-based email client that is one of my older projects. I wanted to add client-side validation to the To:, Cc:, and Bcc: fields in the composition form, but I quickly realized that a simple regular expression match for one valid email address wouldn't ensure that the whole field was valid because there would likely be multiple emails in each of those fields. Plus, each email may or may not have a name in front of it. So here's how I coded the JavaScript function to validated multiple name-formatted emails in real time.

The code works by looking for correctly formatted emails separated by delimiters (semi-colons, in this case). If a string is typed in that isn't complete or correct, the background of the input field changes color to let the user know that they need to correct it. Below is a working demo-- enter your email address, or your email address surrounded by angle brackets:

Enter emails:

I started off by calling a function to validate my fields each time the user pressed a key or used the mouse to change a field. The function is called by two event handlers, onKeyUp and onChange:

<script>
function validateEmails(field, delimiter) {
}
</script>

<input type="text" name="to" value="#FORM.to#" size="30" onKeyUp="validateEmails(this);" onChange="validateEmails(this);">

Now that we're calling our validation function whenever the user alters the input field, we need to start putting code in our function. We'll start by parsing out each address block in the string. To do so, I used the split() function to separate the string, and then I tested each part against a regular expression to make sure it was an email address. If it fails to contain an email address, I turn the input field's background a light yellow color:

<script>
function validateEmails(field, delimiter) {
	var delimiter = delimiter || ';';
	var filter  = /([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+>?$/;	
	var error = 0;

	// Create an array by splitting the field along the delimiter
	var aEmails = field.value.split(';');
	
	// For each of the emails
	for(index = 0; index < aEmails.length; index++) {
		// Trim spaces from the ends
		aEmails[index] = (aEmails[index].replace(/^\s+/,'')).replace(/\s+$/,'');
		// Check whether an email is present
		if(aEmails[index] != '' && aEmails[index].search(filter) == -1)
			error = 1;
	}
	
	// Update the value of the field
	field.style.backgroundColor = (error == 1) ? 'FFFFCC' : 'FFFFFF';
}
</script>

That works great. But what if someone puts their name in front of an email without enclosing the email in angle brackets (i.e., Jane Smith jane@example.com)? So I added a check for angle brackets if there was a name in front of the email address (note that I added a new regexp filter):

function validateEmails(field, delimiter) {
	var delimiter = delimiter || ';';
	var filter1  = /([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+>?$/;
	var filter2  = /<([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+>$/;
	var error = 0;
	
	// Create an array by splitting the field along the delimiter
	var aEmails = field.value.split(';');
	
	// For each of the emails
	for(index = 0; index < aEmails.length; index++) {
		// Trim spaces from the ends
		aEmails[index] = (aEmails[index].replace(/^\s+/,'')).replace(/\s+$/,'');
		// Check whether an email is present
		if(aEmails[index] != '' && aEmails[index].search(filter1) == -1)
			error = 1;
		// Check whether brackets are closed
		else if(aEmails[index].search(filter1) > -1 && aEmails[index].search(/[<>]/) > -1 && aEmails[index].search(filter2) == -1)
			error = 1;
		// Check whether brackets are not present when needed
		else if(aEmails[index].search(filter1) > -1 && aEmails[index].search(/\s/) > -1 && aEmails[index].search(filter2) == -1)
			error = 1;
	}
	
	// Update the value of the field
	field.style.backgroundColor = (error == 1) ? 'FFFFCC' : 'FFFFFF';
}

So now we've handled emails that have names associated with them. But there's one common address format that this doesn't take into account: wrapping the name in quotation marks, which is needed if the name contains a comma. If the user doesn't close their quotes, the email won't be correct. So, we need to add checking for quotes:

function validateEmails(field, delimiter) {
	var delimiter = delimiter || ';';
	var filter1  = /([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+>?$/;
	var filter2  = /<([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+>$/;
	var filter3  = /"[^"]+"\s?<([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+>$/;
	var error = 0;
	
	// Create an array by splitting the field along the delimiter
	var aEmails = field.value.split(';');
	
	// For each of the emails
	for(index = 0; index < aEmails.length; index++) {
		// Trim spaces from the ends
		aEmails[index] = (aEmails[index].replace(/^\s+/,'')).replace(/\s+$/,'');
		// Check whether an email is present
		if(aEmails[index] != '' && aEmails[index].search(filter1) == -1)
			error = 1;
		// Check whether brackets are closed
		else if(aEmails[index].search(filter1) > -1 && aEmails[index].search(/[<>]/) > -1 && aEmails[index].search(filter2) == -1)
			error = 1;
		// Check whether brackets are not present when needed
		else if(aEmails[index].search(filter1) > -1 && aEmails[index].search(/\s/) > -1 && aEmails[index].search(filter2) == -1)
			error = 1;
		// Check whether quotes are closed
		else if(aEmails[index].search(filter1) > -1 && aEmails[index].search(/"/) > -1 && aEmails[index].search(filter3) == -1)
			error = 1;
		// Check whether quotes are not present when needed
		else if(aEmails[index].search(filter1) > -1 && aEmails[index].search(',') > -1 && aEmails[index].search(filter3) == -1)
			error = 1;
	}
	
	// Update the value of the field
	field.style.backgroundColor = (error == 1) ? 'FFFFCC' : 'FFFFFF';
}

That's the final function. Note that it handles only one delimiter at a time, and it can't use a comma as a delimiter-- because the split() function can't tell which commas are wrapped in quotes as part of a name and which aren't.

If anyone has any comments or improvements, I'd be happy to hear them!

Post a comment


Type the characters you see in the picture above.