

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!
Create your free account to unlock your custom reading experience.