Creating a Better User Experience with Regular Expressions

Written by ethan.jarrell | Published 2017/12/27
Tech Story Tags: javascript | web-development | programming | user-experience | web-design

TLDRvia the TL;DR App

I recently started creating a site where a user would input a city and state or zipcode, and have weather results returned to them for that location. I realized, in doing this, some of the difficulties in refining user input for example, from the user, I need to get either a city & state or a zipcode. I could have simply done something like this:

A simple search, with three separate inputs and nice little labels. However, from a user experience point of view, most users would rather not have to use 3 separate inputs. So we’ll just change it right? We’ll make one search bar, and let the user input the information into one search bar. Easy enough right? We can just grab the City, State & zipcode from the one search bar. What if the user formats their input like this:

If every user inputs his response exactly in this format, we could simply push it into an array and refine the response based on each elements location in the array. [0] will be city, [1] will be state, etc. But what if the response is formatted like the following:

Or even like this:

The point is, there’s no way to guarantee what a user will do. So what we need to do is write some logic that will take whatever is in the input, figure out what the state is, and the city, and separate the two. Since the zipcode is optional, as the user can either do city & state or zipcode, we would prefer a zipcode since it is more specific. For that, we could simply write a statement that loops over the input and says, “Okay, if you see a string that contains 5 or more numbers in a row, isolate that and get rid of everything else.”

Easy enough. But let’s focus on how we could isolate the city and state, if that’s what the user decides to use.

Here’s my thought process: The state will be easier to isolate, since there are only 59 possibilities of states and territories. If we have an array of all 59 states, we can match the user input against that array, and if it matches, we’ll use that as our state. I’ll want to refine it later to use the two letter abbreviation for each input, but we’ll get to that later.

Next, the city. This is tricky because some states and cities contain similar or identical words, like, “New York, New York”, “Alabama City, Alabama”, “Indianapolis, Indiana”, etc. It would be easy for our code to get confused between which is the state and which is the city, and we could easily return the user something they don’t expect, if we aren’t careful.

To avoid this, I’m also going to get an array of JSON objects, which contains all the cities associated with each state. That way, once the code has conclusively isolated the state, I can simply take the rest of the data, and see if it matches anything in the array of cities for that state.

Lastly, I’ll need to do some re-formatting. The API I’m using wants the city and state in this format:

San_Francisco & CA,

So once we’ve figured out what is what, we can just format the data the way we need. Let’s get started. First for the city/state JSON object. That file is going to be the largest one I’ll work with here, and so as soon as the page loads, before the user even starts inputting anything, I want to go ahead and load that in the background to save time. I found a JSON object on github that I liked, which had most of the data, but I re-formatted it so it was closer to what I wanted. Here’s the link, if you want to download it and follow along: https://www.dropbox.com/s/uvmjnoql2lo5sqk/weather.json?dl=0

I’ve uploaded this file to my mlab database and now I’ll use fetch to load that as soon as the document loads:

fetch('https://ethans_cute_web_app_api_blah_blah_blah').then(function(response) {if (response.status != 200) {window.alert("Oopsie. You must have done something wrong.");return;}

response.json().then(function(data) {

let states = data;

Remember the variable “states”, because we’re going to leave that alone and come back to it later. We’re really only using this to figure out the cities.

Now let’s figure out the state.

First, I’m saving some of the variables that I’ll want to use for later.

let input = document.getElementById('input');let current = document.getElementById('current');

Following that, I’ll start my function where I’ll begin getting and refining the user input.

current.onclick = currentAPI;

function currentAPI() {  
    
  let inputValue = input.value;

My thought process for this is that the user is probably going to either type out the entire state, or the two character abbreviation. So I’m going to search for either possibility. First, I’ll look for the two character abbreviation. Here’s an array of all 59 state and territory abbreviations. It’s going to go right inside our currentAPI function:

let stateAbbr = [ "AK","AL","AR","AS","AZ","CA","CO","CT","DC","DE","FL","GA","GU","HI","IA","ID","IL","IN","KS","KY","LA","MA","MD","ME","MI","MN","MO","MS","MT","NC","ND","NE","NH","NJ","NM","NV","NY","OH","OK","OR","PA","PR","RI","SC","SD","TN","TX","UT","VA","VI","VT","WA","WI","WV","WY"]

Next, we’ll check and see if there’s an abbreviation in the input. We’ll do that by looping over the input, splitting it up into individual text blocks. Then we’ll loop over each one and see if any of them are two characters long. If so, we’ll compare it against our state abbreviations. If there’s a match, we’ll save it as a variable. Here’s how I wrote that logic:

let possibleState = [];for (var i = 0; i < text2.length; i++) {if (text2[i].length == 2) {possibleState.push(text2[i])}}

So here, if the user input any two character strings, it’s now in our “possibleState” array. If not, our array is empty. We’ll go ahead and assume there’s something there, convert it to a string and test it against our state abbreviation array.

let state2 = possibleState.toString();

  let uppercaseState = state2.toUpperCase();  
    
  let confirmedState = "";  
  for (var i = 0; i < stateAbbr.length; i++) {  
    if (stateAbbr\[i\] == uppercaseState) {  
      confirmedState = stateAbbr\[i\];  
    }  
  }

There we go. What I’ve done is here is taken our possibleState array, which may hole a state abbreviation, and I’ve converted anything in the array to a string. Next, I’ve converted it to UpperCase, since the array we’re using for a comparison is uppercase. We want to make sure we’re comparing apples to apples. Then I’m looping through the stateAbbr array, and if any of the states in the array match our possible state now in the variable uppercaseState, we’ll let it be defined as the variable confirmedState, because now we’ve confirmed that this is most likely a state, if there’s any data there. Now, there still may be nothing there, if the user didn’t input any two character strings. If there is something there, that’s the one we want to use. If not, we’ll want to search for a typed out state. So what I’m going to do is go ahead and look for a typed out state name. Then I’ll create a variable called “myState”. And I’ll say, “If there’s something in the possibleState Array, then myState is the two letter abbreviation, if not, then it’ll be the typed out state.” Here’s how I’ll search for the typed out state:

let statesArray = ["alaska","alabama","arkansas","americansamoa","arizona","california","colorado","connecticut","districtofcolumbia","delaware","florida","georgia","guam","hawaii","iowa","idaho","illinois","indiana","kansas","kentucky","louisiana","massachusetts","maryland","maine","michigan","minnesota","missouri","mississippi","montana","northcarolina","northdakota","nebraska","newhampshire","newjersey","newmexico","nevada","newyork","ohio","oklahoma","oregon","pennsylvania","puertorico","rhodeisland","southcarolina","southdakota","tennessee","texas","utah","virginia","virginislands","vermont","washington","wisconsin","westvirginia","wyoming"];

Similar to the abbreviations, I’ve got an array of all lowercase states with no spaces in between them.

let text = inputValue.replace(/\W+/g, "");

  let inputLowerCase = text.toLowerCase();

  let inputUpperCase = text.toUpperCase();

So here, I’ve taken the entire user input, taken out all the spaces and characters like commas, etc. Then I’ve converted it all to lowercase characters. Now I need to compare the two.

let myState = "";

for (var j = 0; j < inputLowerCase.length; j++) {for (var i = 0; i < statesArray.length; i++) {if (inputLowerCase.includes(statesArray[i])) {myState = statesArray[i];}}}

There. I’m comparing the two . Keep in mind our input could contain a lot of different things, so we’re just looping through and seeing if any item in the states array is included in the total string of input. So if the input includes any of the items from the states array, we’ll get it. Finally, we need to decide whether we’re going to use the abbreviation, if the user gave us one, or the typed out state, if the user gave us that. Here’s how I’ll check for that:

let stateToUse = "";if (possibleState.length == 0) {stateToUse = myState;} else {stateToUse = confirmedState;}

Okay, so now, either way, we’ve got our state. However, if the state isn’t an abbreviation, I want to convert it to one. We know that whatever “stateToUse” is, it’s either an abbreviation, or the typed out state, lowercase, with not spaces.

let stateMatch = {"alaska": "AK","alabama": "AL","arkansas": "AR","americansamoa": "AS","arizona": "AZ","california": "CA","colorado": "CO","connecticut": "CT","district of columbia": "DC","delaware": "DE","florida": "FL","georgia": "GA","guam": "GU","hawaii": "HI","iowa": "IA","idaho": "ID","illinois": "IL","indiana": "IN","kansas": "KS","kentucky": "KY","louisiana": "LA","massachusetts": "MA","maryland": "MD","maine": "ME","michigan": "MI","minnesota": "MN","missouri": "MO","mississippi": "MS","montana": "MT","northcarolina": "NC","northdakota": "ND","nebraska": "NE","newhampshire": "NH","newjersey": "NJ","newmexico": "NM","nevada": "NV","newyork": "NY","ohio": "OH","oklahoma": "OK","oregon": "OR","pennsylvania": "PA","puertorico": "PR","rhodeisland": "RI","southcarolina": "SC","southdakota": "SD","tennessee": "TN","texas": "TX","utah": "UT","virginia": "VA","virginislands": "VI","vermont": "VT","washington": "WA","wisconsin": "WI","westvirginia": "WV","wyoming": "WY",};var re = new RegExp(Object.keys(stateMatch).join("|"), "gi");keyWord = stateToUse.replace(re, function(matched) {return stateMatch[matched.toLowerCase()];});

let state3 = keyWord.toUpperCase();

So here, I’m converting the second possibility into an abbreviation. At this point, no matter what the user’s input was, no matter how the capitalized it or even if they included spaces in between characters. No matter what, at this point, we have the Two letter abbreviation of their state. Perfect. Now for the tricky part. The city.

Remember that JSON data we loaded first? Here’s what that data looks like:

{

"state":"AL",

"city":["ABBEVILLE", "ADAMSVILLE", "etc", "etc", "etc"]}

And we have that for each state. Basically every city, in every state, and the data is in a variable called “states”. Here’s how I’ll start to loop over it:

let myCity = "";for (var i = 0; i < states.length; i++) {if (states[i].state == state3) {for (var k = 0; k < states[i].city.length; k++) {if(text4.includes(states[i].city[k])) {myCity = states[i].city[k];}}}}

I’m using a nested for loop to test our abbreviated states against the states in our JSON array, to see if we get a match. Once we get a match, we’re saving it in the variable, “myCity.”

Now we need to format it. If the city is “charlotte”, we want it to give us “Charlotte” with the first letter uppercase. But what if we have “san francisco? We want it to return this: “San_Francisco”, capitalizing the first letter of each word, separated by an underscore.

let twoCity = myCity.toLowerCase();let threeCity = [];let string3 = [];for (var i = 0; i < twoCity.length; i++) {if (twoCity[i] == " ") {let newCity = twoCity.split(" ");threeCity.push(newCity);} else if (twoCity.includes(" ") == false && threeCity.includes(twoCity) == false) {let newCity2 = twoCity;threeCity.push(newCity2);}}

To start off, we convert the entire string to lowercase, then split it at spaces. That way we now have an array, “threeCity”. In the array is our city name. I’m also creating two scenarios in my if statement within the for loop. One for the case that it’s a two word city by saying,” if the input string contains a space, do something.” Then, the second scenario is that we’re dealing with a one word city, so I’m saying, “if the input doesn’t contain a space.” If the city name is more than one word, each word is it’s own array item.

let string4 = [];for (var i = 0; i < threeCity.length; i++) {String.prototype.capitalize = function() {return this.charAt(0).toUpperCase() + this.slice(1);}if (threeCity.length <= 1) {let cityString = threeCity.toString();let newWord = cityString.capitalize();string4.push(newWord);} else if (threeCity[0].length > 1) {for (var i = 0; i < threeCity[0].length; i++) {let newWord2 = threeCity[0][i].capitalize();string4.push(newWord2);}}}

let string5 = string4.toString();let string6 = string5.replace(",", "_");

Here, I’m taking the threeCity array, and running it through a function that capitalizes the first letter in each array item. Now, if our city is one word, two words, or 5 words or whatever, each word is capitalized.

Then I convert the array to a string, which means I now have each word capitalized, separated by commas. So I simply to a string replace method, replacing the commas with underscores.

Ta-da! This may be a little over-complicated. And I’m sure there’s a much simpler way to do it. But it works, and runs pretty fast. And it’s great, because I can be pretty sure that no matter what the user inputs, or how badly their input is formatted, I’ll get back the state and city. (As long as my city arrays are up to date).

Now I can use that city and state to grab data from the weather api, which was my original intention anyway. But I’ve made the process simpler for the end user, who can now input everything in one, simple search bar.

Let me know if you have any feedback. Thanks!


Published by HackerNoon on 2017/12/27