Hello!

I have a text field where a user can enter either a(n):

  • American zip code (example: "02475")
  • Canadian zip code (example: "A3F 4G6")
  • A "city/state abbreviation" combination (example: "Toronto, ON", or "Las Vegas, NV")

For the most part, users don't have much of a problem with the first two criteria, but some people have some trouble with the third, either by entering simply a state ("NB", or "Texas"), a city, or spelling out the state name "Chicago, Illinois". I need to put together some validation that makes sure that:

  • If the entered text is not an american or canadian zip code (IOW, if the entered text contains no numbers at all - in that case, move on to the next validation check)
  • Check to see if the third to last character is non alphanumeric (if they entered the city/state combination correctly, there would be some sort of non-alphanumeric separator between the city and state abbreviation)
  • if the entered text doesn't fit the criteria, return the error.

This is what I think I've got for code, but I"m sure the syntax is off. Is there a chance someone could please point me in the right direction? Thanks!

function hasNumbers(string) {
    var regex = /\d/g;
    return regex.test(string);
}

function validateOriginFormat() {
    var error = "";
 
    if (
      (document.form.Search_Type_1.checked == true)&&
      (!hasNumbers(document.form.Search_Radius_Point.value))&&//if the value of the field has no numbers in it, and, thusly is not an american or canadian zip code
      (document.form.Search_Radius_Point.value.substr(-3, 1)//and the third to last character of the value is not a letter or number
      )
   {
        error = "Please use a valid city/state format (example: Boston, MA or Toronto, ON) in Step 3!\n"//recognize the the intended input was a city and state, but the proper format of "City, ST" was not used.
        }
    return error; 
}

You could try :

val.substr(val.length-3, 1).match(/[A-Z]/i)

There's no point testing for numbers again because !hasNumbers(val) has already allowed anything with numbers to fall through this error trap.

This would be a more rigorous test :

!val.match(/^[a-zçáéíóäëiöúàèììù]+[, ]+[a-z]{2}$/i)

Read this as :
"val does not consist of
start of string
an a-z group (including accented characters) of one or more characters, followed by
a comma or space group of one or more characters, followed by
an a-z group of exactly two characters
end of string
(everything case insensitive)"

Accented characters are included in the first alphabetic group to allow for eg. French Canadian place names(?). There are probably more accents than you would ever need for US/Canadian place names - but I am no expert.

Airshow

You could try :

val.substr(val.length-3, 1).match(/[A-Z]/i)

There's no point testing for numbers again because !hasNumbers(val) has already allowed anything with numbers to fall through this error trap.

This would be a more rigorous test :

!val.match(/^[a-zçáéíóäëiöúàèììù]+[, ]+[a-z]{2}$/i)

Read this as :
"val does not consist of
start of string
an a-z group (including accented characters) of one or more characters, followed by
a comma or space group of one or more characters, followed by
an a-z group of exactly two characters
end of string
(everything case insensitive)"

Accented characters are included in the first alphabetic group to allow for eg. French Canadian place names(?). There are probably more accents than you would ever need for US/Canadian place names - but I am no expert.

Airshow

Heh..wow...that would cover a lot right there.. thanks for the suggestion :-D

From what I'm intending, if someone is attempting to enter a city/state combination, I want the script to look at the third from last character. If they entered the combo correctly, that character will be a separator of some sort (maybe a space, maybe a comma, maybe even a period) between the city and the two-letter state abbreviation. If the third from last character is a letter (if it was a number, it would have been accounted for in one of the first two conditions), it should return an error.

Does that explain myself a little more? Or am I not grasping something you're telling me to do (which is just as, if not more, possible).

Thanks for your help!

Jay,

It's OK, I think I understand the issue but I gave you two solutions and they are both in a sort of shorthand where I replaced document.form.Search_Radius_Point.value with val In full, the test for the third from last character being alphabetic would be:

document.form.Search_Radius_Point.value.substr(document.form.Search_Radius_Point.value.length-3, 1).match(/[A-Z]/i)

Airshow

Jay,

It's OK, I think I understand the issue but I gave you two solutions and they are both in a sort of shorthand where I replaced document.form.Search_Radius_Point.value with val In full, the test for the third from last character being alphabetic would be:

document.form.Search_Radius_Point.value.substr(document.form.Search_Radius_Point.value.length-3, 1).match(/[A-Z]/i)

Airshow

Ah, I think I understand, so the proper way of going about it would be:

function hasNumbers(string)  //check to see if the string has any digits in it
{
	var regex = /\d/g;
	return regex.test(string);
} 

function validateOriginFormat() {
    var error = "";
 
    if (
		(document.form.Search_Type_1.checked == true)&& //if the first type is selected
		(!hasNumbers(document.form.Search_Radius_Point.value))&&  //and if the value of the field has no numbers in it, and, thusly is not an american or canadian zip code
		(document.form.Search_Radius_Point.value.substr(document.load_search.Search_Origin_Radius_Point.value.length-3, 1).match(/[A-Z]/i)  //and the third to last character of the value is not a letter or number
		)
	{
        error = "Please use a valid city/state format (example: Boston, MA or Toronto, ON) in Step 3!\n"  //recognize the the intended input was a city and state, but the proper format of "City, ST" was not used.
    }
    return error;  
}

Do I have that right?

Actually, I think I found a way to pare down the complexity of the statement even more:

American zip codes use a 5-number format (3rd to last char is *always* a number (aka "never a letter"))
Canadian zip codes follow a "A1A 1A1" format (again, 3rd to last char is *always a number (aka "never a letter"))
In a proper "City, ST" entry for the USA or Canada, the 3rd to last char should always be a separator between the city and state abbreviation (again, AKA "never a letter")

So, basically, I just need to make sure that the third to last character is never a letter in all instances. Otherwise, the formats should validate fine. Does this sound right to you, or could I be painting with too broad a brush?

Jay,

Post/zip code validation is quite a large topic. Personally, I would do an internet search for regular expressions to do the job. They can be very sophistocated and precise. A DIY approach is unlikely to match the quality of tried and tested tests.

I know most about UK post code validation. A quick browse through the Wikipedia article will tell you that it's not straightforward.

Try Googling "US Canada zip code validation" and you will find a bunch of good discussions and regexes.

Airshow

I know most about UK post code validation. A quick browse through the Wikipedia article will tell you that it's not straightforward.

Airshow

Hehe..after wracking my brain for the past week over this, I can definitely agree with you. I already understand the formatting of the postal codes of the two countries I'm working in. I think my main challenges are:

  • validating one text field against 3 different criteria
  • in a text field where the user is expected to enter letters, numbers, and non alpha-numeric characters, trying to eliminate the nearly infinite number of things they can do *wrong*, while trying to not write bloated code, can be nearly impossible. At this point, I just need to make sure that if what they're typing in isn't a zip code in either country, what they did type in follows at least a "xxxxx xx" or "xxxxx,xx" or "xxxxx, xx" format.

Either way, I did do some searching like you suggested, and did come up with one, really simple script that checks against US and Canadian postal codes (sharing, if you're interested in maybe exploring this for your own benefit later):

function validZip() {
   var zip = document.getElementById('zipcode').value;
   var zipRegExp = /(^\d{5}$)|(^\D{1}\d{1}\D{1}\s\d{1}\D{1}\d{1}$)/;
   return zipRegExp.test(zip);
}

Just about all the results I've found readily validates the zip codes without a problem, it's that third if/then statement, making sure that people type in their City/State combinations properly, that I can't seem to find any results on.

I'm thinking that, given passing the input data through the function above evals to FALSE, it's then time to assume that the entered value is supposed to be a city and state.

  • Since the character length in a city name is variable, I can't work from the beginning of the string, I have to begin working from the end.
  • Since the last last 2 letters of some full state names could turn out to be the 2 letter abbreviation of an actual state (the last 2 letters of "California" is the abbreviation for Iowa, and the last 2 letters of "Maryland" is the abbreviation for North Dakota), I can't simply match the last two letters against a list of states and provinces, as that could evaluate to a false negative, should the user type in a full state name instead of an abbreviation.
  • Since I want the user to use the US and Canadian standard two-letter abbreviation, and that abbreviation should always come at the end of the string, I can safely say that the character preceding it (the third to last character) would be some sort of non-alphanumeric character- be it a space, comma, or period.

Sorry, I'm kind of thinking aloud to myself, here, just to get my own thoughts straight. Either way, thank you for all your help on this matter - it's already given me some perspectives on the issue I hadn't thought of!

Jay,

What ever approach you adopt, you will need to do some testing to make sure the validator does what it is supposed to. Here's something that will help with the testing, and should also give some clues on how you might like to organise your code:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Airshow :: Zip code etc. Regex trials</title>
<style type="text/css">
body { font-family: verdana; }
#wrapper {
	width: 300px;
	border: 2px solid #669999;
	background-color: #e0f0F0;
	text-align: center;
}
hr {
	height: 0;
	border-top: 1px solid #669999;
}
h1 {
	margin: 0;
	padding: 3px;
	background-color: #99C9C9;
	border-bottom: 2px solid #669999;
	color: #004545;
	font-size: 15pt;
}
h2 {
	margin: 5px 0 0 0;
	padding: 0;
	color: #004545;
	font-size: 8pt;
}
p {
	margin: 0;
}
#innerWrapper {
	padding: 10px;
}
#innerWrapper div {
	padding: 5px 0;
}
#myTxt {
	text-align: center;
}
#checkIt {
}
.message {
	width: 150px;
	height: 1.0em;
	padding: 5px;
	border: 2px solid #669999;
	background-color: #f0ffff;
	font-size: 10pt;
	font-weight: bold;
	text-align: center;
}
</style>

<script>
onload = function(){
	var input = document.getElementById('myTxt');
	var ch = document.getElementById('checkIt');
	var message_us = document.getElementById('message_us');
	var message_can = document.getElementById('message_can');
	var message_city_state = document.getElementById('message_city_state');
	var message_overall = document.getElementById('message_overall');
	var true_message = 'OK';
	var false_message = 'Not OK';

	var us_zip_check = function(val){
		var pattern = /^\d{5}([\-]\d{4})?$/;//source: http://www.breakingpar.com/bkp/home.nsf/0/87256B280015193F87256F6B005294C2
		return pattern.test(val);
	}
	var can_zip_check = function(val){
		var pattern = /^\s*[a-ceghj-npr-tvxy]\d[a-ceghj-npr-tv-z](\s)?\d[a-ceghj-npr-tv-z]\d\s*$/i;//source: http://home.cogeco.ca/~ve3ll/jstutorf.htm#pc
		return pattern.test(val);
	};
	var city_state_check = function(val){
		var pattern = /^[a-zçáéíóäëiöúàèììù]+[, ]+[a-z]{2}$/i;//Source: Airshow
		return pattern.test(val);
	};
	ch.onclick = function() {
		usZipCheck = us_zip_check(input.value);
		message_us.innerHTML = (usZipCheck) ? true_message : false_message;

		canZipCheck = can_zip_check(input.value);
		message_can.innerHTML = (canZipCheck) ? true_message : false_message;

		cityStateCheck = city_state_check(input.value);
		message_city_state.innerHTML = (cityStateCheck) ? true_message : false_message;

		message_overall.innerHTML = (usZipCheck || canZipCheck || cityStateCheck) ? true_message : false_message;
	}
}
</script>
</head>

<body>

<div id="wrapper">
	<h1>Zip code regex trials</h1>
	<div id="innerWrapper">
		<div><input id="myTxt" size="40" /></div>
		<div><input type="submit" id="checkIt" value="Check it" /></div>
		<hr>
		<h2>US Zip</h2>
		<p class="message" id="message_us">&nbsp;</p>
		<h2>Canada Zip</h2>
		<p class="message" id="message_can">&nbsp;</p>
		<h2>City, State</h2>
		<p class="message" id="message_city_state">&nbsp;</p>
		<hr>
		<h2>Overall</h2>
		<p class="message" id="message_overall">&nbsp;</p>
	</div>
</div>

</body>
</html>

Please note, I'm not recommending the coded regexs, they are just ones I either found after a quich search, or (in the case of the city-state check) wrote myself according to your own wriiten rules. You may disagree with them and quite possibly you will find better somewhere on the web, in which case, edit the patterns. (Keep earlier versions by //commenting out rather than deleting - so you have a record of what you already tried).

When you are satisfied with the checks, then transfer the code from the test page into your target file.

Habe fun.

Airshow

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.