24
AJAX Demystified - What Is AJAX?
In Part 1 of this series, we laid the foundation for learning AJAX by digging deep into the history of web development at the time when AJAX was created. In this next part of the series, we will learn what AJAX really is.
We learned from the previous blog post that AJAX was created to solve the problem with the "annoying" delay in the responsiveness of a sovereign web application. An AJAX application, therefore, eliminates the start-stop-start-stop nature of interaction on the Web by implementing a collection of four technologies that complement one another: CSS, DOM, JavaScript, and XMLHttpRequest.
- JavaScript (JS): JavaScript is the glue that holds everything together in an AJAX application. JS is used to define user workflow and business logic of the app, manipulates and refreshes the UI via the DOM to continually redraw and reorganize the data presented to the users based on user's interactions via mouse and keyboard event handling.
- CSS: In an AJAX application, the styling of a UI may be modified interactively through CSS.
- DOM: The DOM presents the structure of web pages as a set of programmable objects that can be manipulated with JS. Scripting the DOM allows an AJAX application to modify the UI on the fly, effectively redrawing parts of the page.
- XMLHttpRequest: The XMLHttpRequest object allows data retrieval from the webserver asynchronously. The data format is typically XML , but it works well with any text-based data. Nowadays, the most common data format used for this is JSON.
It's worth mentioning that three of the four technologies listed above ( CSS, DOM, JS ) have been collectively referred to as Dynamic HTML (DHTML). DHTML offered the ability to create interactive UI for web pages. However, it never overcame the issue of the full-page refresh since without communicating with the server, there was only so much it could do. AJAX makes considerable use of DHTML, but with the ability to make asynchronous requests to the server, AJAX has a lot more to offer.
XMLHttpRequest is a native API in JS that encapsulates the logic of sending HTTP requests without having to refresh a loaded web page - _ AJAX request. _ Even though developers rarely use XMLHttpRequest directly now, it's still the building block that works underneath many popular HTTP request modules such as Fetch and Axios.
The complete lifecycle of loading a document using XMLHttpRequest is as follows: _instantiate the XMLHttpRequest object, tell it to load a document, and then monitor that load process asynchronously using callback handlers. _
Now that you understand the high-level concept of AJAX, let's jump into some codes.π©βπ» Let's try to implement AJAX using Vanilla JS. Why would we want to do that if there are several frameworks out there that have out-of-the-box AJAX technology? It's because we want to learn "under the hood", remember?π
We will be implementing AJAX using Vanilla JS to build a simple web application as shown below. When the Retrieve button is clicked, a "Hello World!" message returned by the backend server via an AJAX call is displayed right below the button without a page refresh. Try it for yourself!π
A simple AJAX implementation takes four steps:
- Create an instance of the
XMLHttpRequest
(akaXHR
) object - Use the
open()
method of theXHR
to specify what kind of data you want - Create a function to process the response
- Use the XHR's
send()
method to send the request
Now, let's look at some code. For this simple demo, we have a simple backend web server written in Node.js to process AJAX requests. For the UI on the frontend, we have a simple HTML and a script for the Retrieve button's event handling.
The code for our Node.js web server is defined in the index.js
file located in the root directory.
var http = require("http");
var fs = require("fs");
function render(path, contentType, fn) {
fs.readFile(__dirname + "/" + path, "utf-8", function (err, str) {
fn(err, str, contentType);
});
}
//create a server object:
http
.createServer(function (req, res) {
var httpHandler = function (err, str, contentType) {
if (err) {
console.log(err);
res.writeHead(500, { "Content-Type": "text/plain" });
res.end("An error has occured: " + err.message);
} else {
res.writeHead(200, { "Content-Type": contentType });
res.end(str);
}
};
if (req.url.indexOf("/scripts/") >= 0) {
console.log("Serving ajax.js");
render(req.url.slice(1), "application/javascript", httpHandler);
} else if (
req.headers["x-requested-with"] === "XMLHttpRequest" &&
req.headers["x-vanillaajaxwithoutjquery-version"] === "1.0"
) {
console.log("Processing AJAX request");
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "Hello World!" }));
} else {
console.log("Serving index.html");
render("views/index.html", "text/html", httpHandler);
}
})
.listen(8080); //the server object listens on port 8080
Here, we're using the http
module to create an HTTP server. For those of you that are not familiar with Node.js, here is a brief explanation of the code.
The server is listening on Port 8080
. The callback function we pass to the createServer()
method is executed for each request that comes in. Whenever a new request is received, the request
event is called, providing two objects: a request (an http.IncomingMessage
object) and a response (an http.ServerResponse
object). The request
provides the request details through which we can access the request headers
and request data
. The response
is used to populate the data returning to the client.
var http = require("http");
//create a server object:
http
.createServer(function (req, res) {
// callback to handle request is define here
})
.listen(8080); //the server object listens on port 8080
Let's look at the callback function used to handle requests.
if (req.url.indexOf("/scripts/") >= 0) {
render(req.url.slice(1), "application/javascript", httpHandler);
} else if (
req.headers["x-requested-with"] === "XMLHttpRequest" &&
req.headers["x-vanillaajaxwithoutjquery-version"] === "1.0"
) {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "Hello World!" }));
} else {
render("views/index.html", "text/html", httpHandler);
}
This block of code checks the request URL to determine how the app should respond.
- If the request came from the
scripts
directory, then the appropriate file is served with theapplication/javascript
content type. - Any request with
x-request-with
headers set toXMLHttpRequest
indicates an AJAX request. In this case, we simply return a "Hello World!" text message. - If it's neither of the two cases above, then the
index.html
file is served.
We haven't discussed the render()
and httpHandler()
functions yet, so let's do that now. The render()
function asynchronously reads the contents of the requested file. It accepts the httpHandler()
callback function, which is used to check for errors and write the content of the file to the response with the appropriate HTTP status code if everything looks good.
function render(path, contentType, fn) {
fs.readFile(__dirname + "/" + path, "utf-8", function (err, str) {
fn(err, str, contentType);
});
}
<!--kg-card-end: markdown--><!--kg-card-begin: markdown-->
var httpHandler = function (err, str, contentType) {
if (err) {
console.log(err);
res.writeHead(500, { "Content-Type": "text/plain" });
res.end("An error has occured: " + err.message);
} else {
res.writeHead(200, { "Content-Type": contentType });
res.end(str);
}
};
Now, let's look at our frontend code, which contains the /views/index.html
file and the /scripts/ajax.js
script used for the button event handling. The index.html
is straightforward. It references the ajax.js
script which is called when the user clicks the Retrieve button.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Vanilla Ajax without jQuery</title>
</head>
<body>
<h1>Vanilla Ajax without jQuery</h1>
<button id="retrieve" data-url="/">Retrieve</button>
<p id="results"></p>
<script src="/scripts/ajax.js" async></script>
</body>
</html>
The ajax.js
script is where most of the AJAX magic happens.
(function () {
var retrieve = document.getElementById("retrieve"),
results = document.getElementById("results"),
toReadyStateDescription = function (state) {
switch (state) {
case 0:
return "UNSENT";
case 1:
return "OPENED";
case 2:
return "HEADERS_RECEIVED";
case 3:
return "LOADING";
case 4:
return "DONE";
default:
return "";
}
};
retrieve.addEventListener("click", function (e) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
console.log(
"Inside the onreadystatechange event with readyState: " +
toReadyStateDescription(xhr.readyState)
);
if (xhr.readyState === 4 && xhr.status === 200) {
if (xhr.responseType === "json") {
results.innerHTML = xhr.response.message;
} else {
results.innerHTML = JSON.parse(xhr.responseText).message;
}
}
};
xhr.open("GET", e.target.dataset.url, true);
xhr.responseType = "json";
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.setRequestHeader("x-vanillaAjaxWithoutjQuery-version", "1.0");
xhr.send();
});
})();
We followed the five steps to implement AJAX described in the previous sections:
Create an
xhr
instance of theXMLHttpRequest
object using the XMLHttpRequest() constructor.Use the
open()
method of theXMLHttpRequest
object to specify the kind of data being requested. It takes three arguments:Create a callback function to process the response.
XHR
has two properties used to indicate a response from the server:Finally, all we're left to do is send the request. But before we do that, we need to make sure we set the _ request headers _ accordingly to indicate that our request is indeed an AJAX request.
Let's tit it all together - frontend and backend. When we start the server by running npm run start
, the server returns the rendered index.html
page to the browser. Since the index.html
file references the ajax.js
script, the browser issues another request to the server to get this file. Finally, when the user clicks the Retrieve button, the server returns the "Hello World!" message. This workflow can be observed via the server's terminal logger as shown below.
Serving index.html
Serving ajax.js
Processing AJAX request
Below are some examples of AJAX implementations for your references:
- Auto-suggestion: Almost all search engines nowadays have this feature implemented. However, it's not limited to just search engines. In fact, it can be used in any text box in web forms.
- Infinite scrolling: Infinite scrolling is also implemented using AJAX. As the user scrolls downwards, more contents are fetched from the server and displayed on the page.
- Self-Updating Stock Ticker: Most trading sites have the stock tickers updated with the latest rates on the market automatically without reloading the entire page.
- Form Validation: For example, when a user enters an email address to sign up for an account, the user's input is sent to the database to check if it's an existing account. Depending on the server's response, the user is either allowed to proceed to the next step or an error is displayed on the screen. This whole process can be done asynchronously without reloading the whole page.
24