This article was first published on JavaScript in Plain English.
Part 2: Applying the theory to build a demo single-page app that validates zip codes.
Photo by Kelly Sikkema on Unsplash
Table of Contents
WELCOME BACK!
Getting Started
Let's Build It!
Step 1. Unzip the Starter Code Archive
Step 2. Putting the Project Pieces Together
Step 3. Capture Necessary DOM Nodes
Verify that Nodes Are Visible to the DOM
Step 4. Declare the Test String and Regular Expression
Step 5. Use Regex to Return the Valid Zip Code Matches
Step 6. Creating the Helper Functions
The createParagraphText() Helper Function
The createParagraphs() Helper Function
The appendParagraphs() Helper Function
What's Next?
WELCOME BACK!
For those of you who have read the first part in this tutorial series, thanks for hanging in through a theoretical article. It will pay off as we proceed from theory to practice.
In Part 2 we apply what we learned in Part 1, Regular Expressions—a Rite of Passage for Web Developers, which gave a quick introduction to some key concepts of regular expressions.
We will transform theory into practice by building a demo Single Page Application (SPA) for the web.
Getting Started
For those who have not already seen what our SPA will look like, Figure 1 below shows the landing page:
Fig. 1 Application Landing Page
The opening screen above presents the following components:
1. The main heading, which states what the application does—namely,
matching valid zip codes.
2. Immediately below this, a callout box explains the aims of the
application in more detail.
3. Next is a scrolling list of the multiline test string that will be passed to
the regular expression shown below it.
4. Beneath this we have a widget that, on the left side, has a group of 2
vertically-stacked buttons. The top one, Validate Zip Codes, triggers
an event that matches and extracts valid zip codes from the test
string. When clicked, the window to its right is populated with valid zip
codes returned by the regular expression.
5. The second button—Reset—resets the text box to its default state as
seen in Figure 1.
How are we going to do all of this? Let’s think about this for a second.
We provide some pre-fabricated test data to our application (though the principle would remain the same if a user were to upload a file with test data). In either case, the regex engine is presented with the test data and asked to return matches only for valid zip codes.
This is more complicated than it sounds. The regular expression specified to the regex engine must be able to do all of the following:
Make certain that user input consists only of numbers and optionally a hyphen for +4 zip codes. (Obviously in the case of this demo application, there is no user input. But further development of this project could make the application fully interactive.)
No alphabetical, punctuation, or other special symbols—save the hyphen needed in a +4 zip code—may be part of the returned match.
Make certain that no other characters precede or follow either variety of zip codes (5-digit or +4).
In every case, the zip code must start with a sequence of 5 digits.
Optionally, the regex engine must be able to match a 5-character sequence followed by a hyphen and ending with a sequence of 4 numbers.
Finally, no characters must occur after the last 4 digits.
All this seems a tall order. But as we saw in the previous tutorial, regular expressions are more than capable of fulfilling all these requirements.
The moment has finally arrived!
Let’s Build It!
After all this theory, let’s put it into practice and build this application.
To start, please download the starter code.
Step 1. Unzip the Starter Code Archive
The archive is named regex-starter-code.zip You can rename it anything more to your liking, such as regex-demo-web-app.zip. Be sure to keep the .zip extension in the name as some zip archive programs might not recognize the file if the extension is missing.
For purposes of this tutorial, I use Microsoft Visual Studio Code, but you can also use your favorite text editor.
Once you have unzipped the renamed regex-demo-web-app.zip (or whatever name you choose), you will have a file structure similar to what is shown below in Figure 2.
Fig. 2 Project File Structure
Step 2. Putting the Project Pieces Together
Our project is comprised of HTML content, CSS styling, and a single JavaScript file—app.js.
Though I have provided styling for you so that we can focus primarily on JavaScript, we will start at the bare-bones structure provided by the index.html file.
Let’s start by opening the application’s index.html file in your favorite
web browser. For purposes of this tutorial, I use Google Chrome for
its superb developer tools. Figure 3 below shows our barebones
application framework.
Fig. 3. Bare Bones HTML
Figure 3 reveals the barebones structure of the document in which our application resides. Not all that comprehensible. Let’s fix that.
Code Listing 1 below shows a fragment of our index.html file.
<!-- INSERT link to zipcodes.css stylesheet. -->
<title>Regex Demo-Zip Code</title>
<!-- INSERT app.js JavaScript -->
</head>
Code Listing 1. index.html Showing Relevant <head> Content
We will replace the comment lines now with the code that will knit together the three components of our application.
As you know, in order for your browser to render the application landing page as we wish, we have to link our cascading stylesheet to the HTML file.
We also must link our JavaScript code to the HTML file. Follow these steps:
1. Open index.html if you have not already done so.
2. Search for the first comment in Listing 1 above and replace it with the
following code:
<link rel=“stylesheet” href=“./css/zipcodes.css”>
3. Now we integrate the last component of our project. Search for the
second comment in Code Listing 1 above and replace it with the
following code:
<script src=“./js/app.js” defer></script>
The keyword defer in the <script>
tag assures that the browser loads the DOM for the index.html document before script execution begins. This prevents JavaScript from throwing errors because a DOM object references a node that has not yet been defined.
Remember to save both these changes in your index.html file.
4. Refresh your browser.
Great! If all goes according to plan, your web page should appear exactly as in Figure 1.
However, if you click on either of the buttons, absolutely nothing happens at this point. Obviously, this is because we have yet to write any of the code that provides functionality to these buttons.
Step 3. Capture Necessary DOM Nodes
Start by opening the app.js file included in the starter code archive.
At this point, the entire file consists of nothing more than comments. We start by capturing the necessary DOM nodes. We will use the Document Object Model (DOM) to pinpoint the parts of our screen that will need to change when the user interacts with our application.
Which of the HTML elements need to be captured in DOM Objects? First, we can reason that because nothing happens when we click on either button in our application, we need to gain access to those nodes. Thus, we need 2 DOM objects to capture the Validate Zip Codes and Reset buttons.
Next we need two things to happen. We need the default message to the right of the buttons that now reads NO MATCH to disappear and be replaced with a scrolling list of matches returned by the regex engine.
In order to do this, we need to be able to capture the default message so that it may be removed. We also need access to its parent element, which will contain the scrolling list once the default message is removed. Thus we need 2 DOM objects to capture these elements.
In total, we must declare 4 DOM objects to capture the nodes that reference the HTML elements to which we need access.
Figure 4 below shows the relevant lines of HTML containing the elements we will capture in DOM objects:
Fig. 4 Elements to Be Captured in the DOM
The critical pieces of markup in Figure 4 are the buttons, the paragraph reading “no match,” and its parent element—the <div>
with an id attribute of “results.”
Each element has an id property set on it so that it may be captured using the document.getElementById()
method. There are other means that could be employed, such as the querySelector()
and querySelectorAll()
methods.
For this tutorial, I have chosen to use getElementById()
for its directness and simplicity.
To create the necessary DOM objects, search in your starter code for the line that reads // TO BE DONE: Declare necessary DOM nodes
and replace it with the following 4 lines of code:
const resultButton = document.getElementById(‘validate’);
const resetButton = document.getElementById(‘reset’);
const resultBox = document.getElementById(‘results’);
const placeHolder = document.getElementById(‘message’);
Code Listing 2: Code to Capture Required DOM Nodes
Still, nothing will happen if you refresh your browser and click on the Validate Zip Codes button. We have not yet provided this functionality. What we will do now is to test that the elements we captured are really captured, using the Google Chrome Developer Tools.
Verify that the Nodes Are Visible to the DOM
To do this, refresh your browser and open the JavaScript Console by pressing Ctrl+Shift+J for Windows/Linux/Unix and Command+Option+J for Mac. You should see something like Figure 5 below:
Fig. 5 Google Chrome Showing JavaScript Console on Right
To verify our node objects, click an insertion point in the JavaScript Console and type the following commands followed by the Enter/Return key:
resultButton <Enter>
resetButton <Enter>
resultBox <Enter>
placeHolder <Enter>
Be sure to expand the output for resultBox by clicking on the right-pointing arrow next to the collapsed information.
You should see a result like that shown in Figure 6 below:
Fig. 6 Browser with JavaScript Console Confirming Access to the Desired Nodes
As of now, we have our entry point into the application because we can access the portions of the page we need to manipulate. Next, we need to declare our test string and our regular expression.
Step 4. Declare the Test String and Regular Expression
We need to declare two constants—one that will store the test string we pass to our regular expression and one that stores the regular expression object to which the test string will be passed.
Search in your starter code for the line that reads // TBD: Declare testString
and replace it with the following code:
const testString =
`10003
asdf10003
10003asdf
jklm10003^$@%
10003-8924
one zero zero zero three
9101-94015
94015-9101
20012
08735
KbdsD$%^&*
sSd070031jkl;m
70122
\sKu2034
98108
75381
asdfjkl;
60661
!9004!@#$5^&*
97218
#,.$$&&%@
10022-3337
eNuhfF!.` ;
Code Listing 3. The Test String
Search in your starter code for the line that reads
// TO BE DONE: Declare regular expression.
and replace it with the following code:
const regex = /^[0-9]{5}(-[0-9]{4})?$/gm;
The stage is set for our demo application. We have captured the DOM nodes that will be necessary to inject dynamic data to the page when the user clicks on the Validate Zip Codes button. We have also provided our testString constant and our regex constant.
Next, we have to move on to building the functionality that will provide the matches of valid zip codes that we want injected on our page.
Step 5. Use Regex to Return the Valid Zip Code Matches
Before we actually add the necessary code to app.js, let’s take a step back to get a clear understanding of how the matching process will work.
We already have our regex object that was declared above using the object literal notation. This object exposes methods that perform different kinds of searches.
There are several methods exposed by the regex object. You can find them on the Mozilla Developer Network regular expressions page (Using Regular Expressions in JavaScript").
What will be relevant to us, however, is the match()
method. The syntax for this method is:
<string object>.match(<regular expression object>);
Our string object is the constant testString. The match()
method, in turn, takes one argument, and that is the regular expression object.
The return value for match()
is an array comprised of every match returned by the regular expression.
The return value must be returned to something in order for it to be accessible. In the syntax shown above, the object returned by the match() method is not addressable, though memory has been reserved for it.
Once the method is executed JavaScript will garbage collect the values stored therein as soon as it determines that they have not been used or are no longer in use. If you are interested in learning more about this, you might want to consult this brief, but informative article on garbage collection (“Garbage Collection”).
The actual syntax we will employ is as shown below:
const myConstant = <string.object>
.match(<regular expression object>);
Search in your starter code for the line that reads
// TO BE DONE: Save regex match to matches variable.
and replace it with:
const match = testString.match(regex);
This one line of code accomplishes a lot. In one fell swoop, testString is passed to regex (our regular expression object), the pattern match is performed, valid zip codes are extracted, and the results are stored in an array with the identifier of matches. Very powerful stuff.
Now let’s test this out by taking the steps that follow:
1. Refresh your browser. If you have not already opened the JavaScript
Console, do so now.
2. Click an insertion point in the JavaScript Console and type matches,
then hit the Enter/Return Key. Once you expand the content
returned in the console, your display should look like that shown in
Figure 7 below:
Fig. 7 Browser with JavaScript Console confirming valid zip code matches
As Figure 7 confirms there are 11 valid zip code matches returned by the regular expression object.
Obviously, we cannot ask an end-user to enter the JavaScript Console to get the results of this validation process. For this to be a truly dynamic application, we have to build some infrastructure preparatory to creating two event listeners—one that listens for a click on the Validate Zip Codes button, the other for a click on the Reset button.
Step 6. Creating the Helper Functions
Of what is this infrastructure comprised? If we start with the most obvious assumption, we need something to happen when an end-user clicks either of the buttons.
Our application is event-driven. Thus we need to code two event listeners for the buttons. As we will see, these event listeners are powered by several helper functions.
Before we code the first helper, we need to declare an empty array, which its code will populate.
Search in your starter code for the line that reads
// TO BE DONE: Declare empty array for text nodes.
and replace it with:
const paragraphText = [];
The above empty array will be populated by our first helper function:
The createParagraphText() Helper Function
Search in your starter code for the line that reads
// TO BE DONE: Code createParagraphText() helper function.
and replace it with:
const createParagraphText = () => {
matches.forEach( match => {
let matchNumber = matches.IndexOf(match) + 1;
paragraphText.push(
` Match #${matchNumber}: ${match}`
);
};
};
Code Listing 4. The createParagraphText() Helper Function
What is happening in this helper function? Let’s break it down line by line.
The code in createParagraphText()
is comprised of a forEach()
loop. forEach()
is exposed by the Array object. In the code above, our array is the matches constant.
We call the forEach()
method on matches. match, which is found in the argument portion of the method, is a reference to each element found in the matches array, over which it loops.
There is also an anonymous callback function as the second parameter, which extracts data from the matches[]
array created by the match()
method above. In the first line of the callback function, we declare the variable matchNumber, which stores the value of the index + 1 within the matches[]
array.
We call the indexOf()
method on match to get the array index of match in each iteration of the loop. 1 is added to the index value because, as we know, arrays are 0-indexed, and for humans, it makes more sense to refer to matches 1 through 11, rather than 0 through 10.
In the final line of our function, we populate the paragraphText[]
array, using the push()
method exposed by the Array object.
The data that we specify to be pushed to the array for each item makes use of both template literal notation and string interpolation to create an array element that looks like this:
Match #1: 10003
You might be wondering why I didn’t create the full paragraph in one fell swoop. Building the full paragraph comes as a result of our next helper function.
Before we build that, we need to declare another empty array to contain the completed paragraphs.
Search in your starter code for the line that reads
// TO BE DONE: Declare paragraphs array to hold complete paragraphs.
and replace it with:
const paragraphs = [];
The paragraphs[]
array will be populated by complete paragraphs with child text nodes extracted from the paragraphText[]
array.
The createParagraphs()
Helper Function
Search in your starter code for the line that reads
// TO BE DONE: Code createParagrahs() helper function.
and replace it with:
const createParagraphs = () => {
paragraphText.forEach( text => {
const paragraph = document.createElement(‘p’);
paragraph.textContent = text;
paragraphs.push(paragraph);
};
};
Code Listing 5. the CreateParagraphs() Helper Function
So what exactly is this code doing? The entire logic of the createParagraphs()
function is found in a single call to the forEach()
method.
The first line in the loop is:
const paragraph = document.createElement(‘p’);
Let’s think about what’s happening here for a moment.
Each time through the loop, JavaScript finds a text node that is a member of the paragraphText[]
array. This line of code declares a <p>
element to which the text node will be appended as a child.
The next line in the loop is:
paragraph.textContent = text;
This line appends the text node at the current index to the <p>
element created in the previous line.
The final line of code in the loop is:
paragraphs.push(paragraph);
This line of code pushes a complete paragraph to the paragraphs[]
array. This sequence is repeated until the last element within the paragraphText[]
array has been read into the loop.
An array of completed paragraphs now wait to be appended to the resultBox object. One final helper function will accomplish this task.
The appendParagraphs()
Helper Function
Let’s review Figure 1 for a moment. The two buttons shown have to their right a box with a message that reads NO MATCH.
We want to swap the contents of this box now, removing the NO MATCH placeholder message with a scrolling window that will contain as its children all of the paragraphs in the paragraphs[]
array.
Search in your starter code for the line that reads
// TO BE DONE: Code createParagrahs() helper function.
and replace it with:
const appendParagraphs = (parent, children) => {
children.forEach( (child) => {
parent.appendChild(child);
});
};
Code Listing 6. The appendParagraphs() Helper Function
In only 5 lines of code, appendParagraphs()
accomplishes quite a lot. The function takes two arguments, parent and children.
When calling this function, parent refers to resultBox, the node object that captures <div id=“results”>
in our index.html file. children refers to the paragraphs[]
array.
Our final helper function also uses the forEach()
method. Here all elements of the paragraphs[]
array are appended to the resultBox node, repeating this process until all 11 matches have been appended to resultBox.
IMPORTANT: If you try to log these nodes to the JavaScript Console in your browser, you will receive an error message. This is because each of the helper functions we have created up to this point is called by the callback functions of the event listeners we have yet to code.
We have now completed our infrastructure comprised of arrays and our 3 helper functions.
AN IMPORTANT ASIDE: You will note that in the
appendParagraphs()
function, we have a fully abstracted function that is de-coupled from the data sets in our application.
Though this could have been done for all the helper functions, in the interest of brevity, I have given this one example instead. This technique of de-coupling functions from the data on which they operate makes the code more reusable and should be practice as a rule in public application releases.
What’s Next?
When you have completed all the steps above successfully, you can continue on with the third and concluding part of our tutorial—Regular Expressions—a Rite of Passage: Completing the Application.
References
“Regular Expressions - JavaScript: MDN, Using Regular Expressions in
in JavaScript” JavaScript | MDN, Mozilla Developer Network,
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#using_regular_expressions_in_javascript.
“Garbage Collection.” The Modern JavaScript Tutorial, JavaScript.info,
23 May 2022, https://javascript.info/garbage-collection.
Top comments (0)