This tutorial gives a basic rundown of how RequireJS can be used to implement web application via components and dependency injection. The sample application is simple, has a form, seven input fields, and two buttons for submitting and resetting the forms. When submitting the form, the field validations are done, and either an error status display or a success status display will be shown. The status display is extracted as a reusable component. This tutorial shows how all these can be pieced together.
Introduction
Recently, I have started another project. It is a web based application and needed a JavaScript framework to support some simple front end user interactions. I don't need AngularJS because the functionality I needed is simple and there is no need for powerful JavaScript libraries like AngularJS. I thought about using JQuery again, but knowing that using it alone would eventually make a mess, I decided it is best to use several small and good JavaScript libraries together, and make the JavaScript end of things modular. RequireJS can be used for managing these different library components, and it forces me to think about modularizing my code and make components reusable across different areas of the application. In this tutorial, I will discuss the basics on how to use RequireJS. I will discuss two basic concepts:
- dependency injection
- reuse components at different parts of an application
The sample application in this tutorial is pretty simple. The web application is created with Spring Boot. It has no controller. The only function it serves is delivering the static contents: the web pages, and the JavaScript files. The application consists just a web page. It displays a form that allows users to enter a person's contact information, then click "Submit" to send the information to the backend. The page will display a status message to indicate the request has been sent successfully. The application also performs input validations. If the field input is invalid, it will show a status message indicating validation failure for the field.
Status message display can be a reusable component. There can be many locations in one page or many other pages, where validation status message can be displayed. One can repeat the same code over and over in all the JavaScript files that touch these places. Or we can write one reusable function or object that can be used to replace these duplicated code. In this tutorial, I will show how to create such a component. Lastly, the application will demo dependency configuration and injection, which makes the application look like an AngularJS application.
I know this might not make much sense, so how about taking a look at the source code?
The Page Mark-up
Let me start with the HTML page markup. This sample application has just one page which looks like this:
Let me just re-iterate what I have introduced before about this application. The page accepts a person's full name and mailing address. There is no HTML 5 based validation on the input fields (input validation is done through JavaScript code). There are two buttons at the lower side of the screen, allowing the user to submit (click button "Submit") or reset (click button "Clear") the form.
The HTML file for the application page mark up is stored in the file called "index.html". The page markup looks like this:
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<link href="/assets/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="/assets/css/index.css" rel="stylesheet">
<script src="/assets/jquery/js/jquery.min.js"></script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="/assets/requirejs/require.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-offset-1 colsm-10 col-md-offset-2
col-md-8 col-lg-offset-3 col-lg-6">
<h3>Require JS Sample App</h3>
<div class="panel panel-default">
<div class="panel-body">
<form id="testAppForm">
<div id="statusDisp"></div>
<div class="form-group">
<label for="firstName">First Name</label>
<input class="form-control" type="text"
id="firstName" name="firstName" placeholder="First Name">
</div>
<div class="form-group">
<label for="lastName">Last Name</label>
<input class="form-control" type="text"
id="lastName" name="lastName" placeholder="Last Name">
</div>
<div class="form-group">
<label for="addressLine1">Address Line 1</label>
<input class="form-control" type="text"
id="addressLine1" name="addressLine1" placeholder="Address Line 1">
</div>
<div class="form-group">
<label for="addressLine2">Address Line 2</label>
<input class="form-control" type="text"
id="addressLine2" name="addressLine2" placeholder="Address Line 1">
</div>
<div class="form-group">
<label for="city">City</label>
<input class="form-control" type="text"
id="city" name="city" placeholder="City">
</div>
<div class="form-group">
<label for="state">State</label>
<input class="form-control" type="text"
id="state" name="state" placeholder="State">
</div>
<div class="form-group">
<label for="zipCode">Zip Code</label>
<input class="form-control" type="text"
id="zipCode" name="zipCode" placeholder="Zip Code">
</div>
<div class="row">
<div class="col-xs-12 col-sm-6">
<button class="btn btn-success form-control"
type="submit" id="submitBtn">Submit</button>
</div>
<div class="col-xs-12 col-sm-6">
<button class="btn btn-danger form-control"
type="reset" id="clearBtn">Clear</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
...
</script>
</body>
</html>
This is a pretty simple markup, a form with seven input fields. The state field should be a drop down and the states should be loaded from the back end server. Not here. Instead, I did this in a rush and just created it as text box. And I also blanked out the script at the bottom, for now.
Since JQuery came out, we move the event handling methods to the script files or sections. The buttons on the page have the click events, as mentioned before. The Submit button, when clicked will trigger input validation, and status message will be displayed on the top like this:
When all the input data checks out, the click of the "Submit" button will trigger the following status message to be displayed:
In order to get all these to work, I need to add some JavaScript code. The first thing I do is create the bootstrap code, the start-up configuration. Remember the JavaScript section that was blanked out above? That is the bootstrap JavaScript code.
The "bootstrap" for Start-up Configuration
One thing I will point out is in this case when I mentioned "bootstrap", I was not referring to the Bootstrap UI framework, which I also used for this project. What I mean by "bootstrap" is the configuration I will setup so that RequireJS can load up the different components form a working web application. It is different from the Bootstrap framework.
You can find my bootstrap code in the file "index.html" at the very bottom. Here is what it looks like:
require.config({
paths: {
jquery: "./assets/jquery/js/jquery.min",
underscore: "./assets/underscore/underscore-min-1.9.2",
statusDisplay: "./assets/app/components/statusDisplay/statusDisplay",
stapes: "./assets/stapes/stapes-min-1.0.0",
app: "./assets/app/app"
}
});
require(["app"], function(app) {
app.init();
});
There are two parts in this piece of code. The first part is to setup the configuration of all the components. What RequireJS needed to know were two things:
- The name of the component. This name can be considered as a key to a hashmap, and the value associated with the key will be the component.
- Where the JavaScript file is so that RequireJS can load the source code for the component.
In this part, I added two more libraries. One is called Stapes
, it is an upgraded version of the BackboneJS framework. But, I consider it as a wrapper on top of JQuery. The other is the UnderscoreJS
. I used its template capability to render the actual HTML using template strings. This is how I create reusable and interactive HTML components. I will create another tutorial on these two libraries.
The second part is the start up of the actual application. Since I used Stapes JS library for HTML element manipulation. All I had to do is call the initialization code and the UI will take care of the interaction by itself.
This is just the beginning. This application is all about reusable component. Next, I will show you how such reusable component is created.
The Reusable Component
As mentioned before, the status display can be used in many places of an application. It is best to extract it out as a reusable component. And at these locations, or place-holders can be setup so that the back end JS code can insert the reusable component in to form the final output display.
Here is the full source code of this reusable component:
define(["jquery", "underscore"], function ($, _) {
var statusHtml = '<div class="col-xs-12">\n'+
' <div class="<%= alertStyleVal%>"><%= msgToShow%></div>\n'+
'</div>';
var statusTemplate = _.template(statusHtml);
const errorStyle = "alert alert-danger small-alert";
const successStyle = "alert alert-success small-alert";
function renderStatus(outerDiv, msg, style) {
if (outerDiv) {
if (msg != null && msg.length > 0) {
var divToAdd = statusTemplate({
alertStyleVal: style,
msgToShow: msg
});
$(outerDiv).html(divToAdd);
}
}
}
return {
clearRender: function(outerDiv) {
if (outerDiv) {
$(outerDiv).html("");
}
},
renderSuccess: function (outerDiv, msg) {
renderStatus(outerDiv, msg, successStyle);
},
renderError: function (outerDiv, msg) {
renderStatus(outerDiv, msg, errorStyle);
}
};
});
Let me just step through all this madness, one part at a time. The first is this enclosure:
define(["jquery", "underscore"], function ($, _) {
...
});
This is a function call. The function is called define(...)
. It is a function from RequireJS, used to define a component. In this case, the function takes in two parameters, the first is an array, which takes in a few string values, the names of the dependent components. This is where the dependency injection is happening. For this reusable component, I needed JQuery and UnderscoreJS. The second parameter is a function definition, the parameter list has the same order as the names of dependencies. The parameters, "$
" is reference to JQuery and "_
" is reference to UnderscoreJS. With these two, I can use them as how these two frameworks should be used.
Next, I declare some variables and instantiate them:
...
var statusHtml = '<div class="col-xs-12">\n'+
' <div class="<%= alertStyleVal%>"><%= msgToShow%></div>\n'+
'</div>';
var statusTemplate = _.template(statusHtml);
const errorStyle = "alert alert-danger small-alert";
const successStyle = "alert alert-success small-alert";
...
The first couple of lines defines the display HTML source code template. Here:
var statusHtml = '<div class="col-xs-12">\n'+
' <div class="<%= alertStyleVal%>"><%= msgToShow%></div>\n'+
'</div>';
Then I need a template function, which later can be used to inject with some value, and compiled into HTML string to be placed in the final HTML page. Here is how I declare it:
var statusTemplate = _.template(statusHtml);
What I did is call a method called template()
in the UnderscoreJS
library. It takes in the template source value, and returns a function reference. We will see this in action next. The following code defines a function that renders the template source into HTML code, then attaches to an existing HTML element. Here it is::
function renderStatus(outerDiv, msg, style) {
if (outerDiv) {
if (msg != null && msg.length > 0) {
var divToAdd = statusTemplate({
alertStyleVal: style,
msgToShow: msg
});
$(outerDiv).html(divToAdd);
}
}
}
The function does some basic data validation, makes sure the message to be displayed is not null or empty. It also checks to make sure the element that the status display will be attached to is not null as well. Once the checks are good, it uses the template function reference to render the HTML display string. The outer element then is used to attach the newly created HTML display value.
The last part is to return the object that represents the component which I am defining. Here it is:
...
return {
clearRender: function(outerDiv) {
if (outerDiv) {
$(outerDiv).html("");
}
},
renderSuccess: function (outerDiv, msg) {
renderStatus(outerDiv, msg, successStyle);
},
renderError: function (outerDiv, msg) {
renderStatus(outerDiv, msg, errorStyle);
}
};
...
This object which returned has three methods:
- The first one is called
clearRender()
. It takes the reference of the input HTML element, then clears out its inner HTML value. - The second one adds status message of success to the targeted HTML element. The success status message has green background.
- The third one adds status message of error to the targeted HTML element. The error status message has red background.
Now that we have a reusable component, let's see how it is injected into the page component and being utilized.
The Code that Controls the Page Elements
Here comes the complicated part of the sample application. There is a page with seven input fields and two buttons. The buttons must have event handling methods associated. As I have stated in the beginning, I do not want to use JQuery. But it was so popular that it is impossible to abandon it. So I used it as an injectable component, and used mostly as DOM query, as well as appending the new elements as shown in the previous section. In this section, I used Stapes
framework for programming the user interaction.
Stapes
framework is based on BackboneJS. And it was very cool in my opinion. Unfortunately, it is no longer maintained, and probably no one is using this framework. Anyways, it impresses me. And I will probably write a tutorial sometime in this year about it. For this tutorial, all I am using it for is to add event handling to the buttons:
- When user clicks on "Submit", input data validation will happen first, and if there are any issues with the input data, the red error status message will be displayed. If all input data are good, a successful status message will be displayed.
- The other button clears all the input fields.
The file that has all these is called "app.js". Here is the full listing of its content:
define(["jquery", "stapes", "statusDisplay"], function ($, Stapes, statusDisplay) {
var testAppForm = Stapes.subclass({
constructor : function() {
var self = this;
self.$el = $("#testAppForm");
var statusDisp = self.$el.find("#statusDisp");
var firstNameInput = self.$el.find("#firstName");
var lastNameInput = self.$el.find("#lastName");
var addrLine1Input = self.$el.find("#addressLine1");
var addrLine2Input = self.$el.find("#addressLine2");
var cityInput = self.$el.find("#city");
var stateInput = self.$el.find("#state");
var zipCodeInput = self.$el.find("#zipCode");
self.$el.on("submit", function(e) {
e.preventDefault();
if (validateForm()) {
statusDisplay.renderSuccess(statusDisp,
"Your request has been handled successfully.");
}
});
self.$el.on("reset", function(e) {
e.preventDefault();
clearCommentForm();
});
function clearCommentForm() {
statusDisplay.clearRender(statusDisp);
firstNameInput.val("");
lastNameInput.val("");
addrLine1Input.val("");
addrLine2Input.val("");
cityInput.val("");
stateInput.val("");
zipCodeInput.val("");
}
function validateForm() {
statusDisplay.clearRender(statusDisp);
var firstNameVal = firstNameInput.val();
if (firstNameVal == null || firstNameVal.length <= 0) {
statusDisplay.renderError(statusDisp,
"Your first name cannot be null or empty");
return false;
}
if (firstNameVal != null && firstNameVal.length > 128) {
statusDisplay.renderError(statusDisp,
"Your first name is too long, 128 characters or fewer.");
return false;
}
var lastNameVal = lastNameInput.val();
if (lastNameVal == null || lastNameVal.length <= 0) {
statusDisplay.renderError(statusDisp,
"Your last name cannot be null or empty");
return false;
}
if (lastNameVal != null && lastNameVal.length > 128) {
statusDisplay.renderError(statusDisp,
"Your last name is too long, 128 characters or fewer.");
return false;
}
var addressLine1Val = addrLine1Input.val();
if (addressLine1Val == null || addressLine1Val.length <= 0) {
statusDisplay.renderError(statusDisp,
"Your address line #1 cannot be null or empty.");
return false;
}
if (addressLine1Val != null && addressLine1Val.length > 128) {
statusDisplay.renderError(statusDisp,
"Your address line #1 cannot have more than 128 characters");
return false;
}
var addressLine2Val = addrLine2Input.val();
if (addressLine2Val != null && addressLine2Val.length > 128) {
statusDisplay.renderError(statusDisp,
"Your address line #2 cannot have more than 128 characters");
return false;
}
var cityVal = cityInput.val();
if (cityVal == null || cityVal.length <= 0) {
statusDisplay.renderError(statusDisp,
"Your city is null or empty.");
return false;
}
if (cityVal != null && cityVal.length > 48) {
statusDisplay.renderError(statusDisp,
"Your city cannot have more than 48 characters");
return false;
}
var stateVal = stateInput.val();
if (stateVal == null || stateVal.length <= 0) {
statusDisplay.renderError(statusDisp,
"Your state is null or empty.");
return false;
}
if (stateVal != null && stateVal.length > 2) {
statusDisplay.renderError(statusDisp,
"Your state cannot have more than 2 characters");
return false;
}
var zipCodeVal = zipCodeInput.val();
if (zipCodeVal == null || zipCodeVal.length <= 0) {
statusDisplay.renderError(statusDisp,
"Your state is null or empty.");
return false;
}
if (zipCodeVal != null && zipCodeVal.length > 12) {
statusDisplay.renderError(statusDisp,
"Your zip code cannot have more than 12 characters");
return false;
}
return true;
}
}
});
return {
init: function() {
new testAppForm();
}
};
});
You should know what this does:
define(["jquery", "stapes", "statusDisplay"], function ($, Stapes, statusDisplay) {
...
});
It injects JQuery, Stapes
, and my component statusDisplay
into this component. RequireJS uses defined to register this as another component.
Next, I declare a Stapes
class type. Here is how I did it:
var testAppForm = Stapes.subclass({
constructor : function() {
...
}
});
Note that I said, it is a type that I am creating, not an object. For this new type, it only contains one method, the constructor. Inside this constructor, the first thing I did is get the handles to the input fields and the buttons, like this:
...
var self = this;
self.$el = $("#testAppForm");
var statusDisp = self.$el.find("#statusDisp");
var firstNameInput = self.$el.find("#firstName");
var lastNameInput = self.$el.find("#lastName");
var addrLine1Input = self.$el.find("#addressLine1");
var addrLine2Input = self.$el.find("#addressLine2");
var cityInput = self.$el.find("#city");
var stateInput = self.$el.find("#state");
var zipCodeInput = self.$el.find("#zipCode");
...
As shown here, I had to use JQuery to get these input field handles. And save these as part of this type I am creating. As for the buttons, I attach the click event handing methods, just like this:
...
self.$el.on("submit", function(e) {
e.preventDefault();
if (validateForm()) {
statusDisplay.renderSuccess(statusDisp,
"Your request has been handled successfully.");
}
});
self.$el.on("reset", function(e) {
e.preventDefault();
clearCommentForm();
});
...
As shown, all these are JQuery code, how to query the HTML elements, and how to attach the event handling to the buttons. For these two buttons, in the event handling methods, the first thing is calling the event object "e
" preventDefault()
method. This will stop the buttons from performing the actual submit or reset functionalities. Then the method would perform the customized functions.
In the above code snippet, you can see the status display reusable component being used. In our page, there is this <div>
:
<div id="statusDisp"></div>
In my code above, it gets a reference to this div
:
...
var statusDisp = self.$el.find("#statusDisp");
...
Lastly, the code can add the status display to the page:
...
statusDisplay.renderSuccess(statusDisp, "Your request has been handled successfully.");
...
For the Submit button, there is the validation of the input fields. Any validation fails will trigger the display of error status message. And if all input fields validation succeeds, the success message will be displayed, simulating the supposed data transmission to the back end server.
The data input validation is long and boring. I will only show part of this method, validateForm()
:
function validateForm() {
statusDisplay.clearRender(statusDisp);
var firstNameVal = firstNameInput.val();
if (firstNameVal == null || firstNameVal.length <= 0) {
statusDisplay.renderError(statusDisp,
"Your first name cannot be null or empty");
return false;
}
if (firstNameVal != null && firstNameVal.length > 128) {
statusDisplay.renderError(statusDisp,
"Your first name is too long, 128 characters or fewer.");
return false;
}
...
return true;
}
Finally, the function of clearing the input fields, all it does is set the value of all the fields to empty strings:
function clearCommentForm() {
statusDisplay.clearRender(statusDisp);
firstNameInput.val("");
lastNameInput.val("");
addrLine1Input.val("");
addrLine2Input.val("");
cityInput.val("");
stateInput.val("");
zipCodeInput.val("");
}
That is all there is for the type I created using Stapes
. Next, I will return the component so that it can be used in the page HTML. Here it is:
define(["jquery", "stapes", "statusDisplay"], function ($, Stapes, statusDisplay) {
...
return {
init: function() {
new testAppForm();
}
};
});
This is the app component. It just has one method called init()
. And all it does is instantiate an object from the Stapes
type which I created. How could it even work? Well, it works because the type constructor will find all the HTML element handles from the page, and attach all the event handlers. So all the references to the elements and event handling methods are saved until the page unloads.
How to Start up the Application
Now circle back to the page source code. The JavaScript source code section has this:
...
require(["app"], function(app) {
app.init();
});
...
The call to RequireJS' function require()
basically invokes the function that is supplied as the second parameter. The first is the array of dependencies. The supplied function calls the object app's method init()
, which in term create an instance of the type I created using Stapes
. The instantiation will set up the event handling on the page. As shown, the whole setup of this application is really simple.
Now that everything about this application has been discussed, let's look at how to test this application.
How to Build and Test
Before you build this application, please rename any files in the sample project from *.sj to *.js.
Once you get the sample project, and unpack it locally to your computer, you need to have Java 1.8 and Maven 3.0 or above to compile. The way you compile the sample object is run the following command:
mvn clean install
After the build completed successfully, you can run the following command to start up the web application:
java -jar target/testapp-0.0.1-SNAPSHOT.jar
Once the command starts up successfully, you can use your browser to test the application by going to the following URL:
http://localhost:8080/
If the start-up command works successfully, the page should look like the screenshot #1 at the top. You can enter some information on the page, then click on the Submit button to see what status message will be displayed.
Summary
In this tutorial, I have given a basic rundown of how RequireJS can be used to implement web application via components and dependency injection. With RequireJS, one can easily break the application into different components, and then stitch them together into a working application. Modularizing an application is always a good idea. It breaks the application into different gears, that can fit together, and some of these can be reused at different places of the same application. Modularizing can make the application clean, uncluttered, and sometimes easy to test.
I have used JQuery for a long time, was never satisfied with it being the only framework used in many complex application, and horrible compromise was used and ugly code segments were fitted in place. This time, I decided to do things differently, so I chose RequireJS, Stapes, and UnderscoreJS. It really doesn't matter if a framework is outdated or no one is using it. As long as it can serve a purpose in an application, then it can and should be used. I never used any of these three libraries. So it put these to use in this simple sample application. And I feel these libraries are quite effective.
The sample application I have throw together was a conceptual application I just pieced together. I will transfer all I have done here into the application I built and am still building. In this sample application, the application is simple, it has a form, seven input fields, and two buttons for submitting and resetting the forms. When submitting the form, the field validations are done, and either an error status display or a success status display will be shown. The status display is extracted as a reusable component. This tutorial showed how all these can be pieced together. It is not much. To me, these libraries show great potential on how these can be properly used to create a great application.
History
- 9th February, 2020 - Initial draft