The University of Arizona
AJAX Example

AJAX Example #

Overview #

Here's a walkthrough of a very basic example of a webpage accessing data using JavaScript's XMLHttpRequest object, and the newer fetch API. Through the twists and turns of web standards over the years, this has become the way that JavaScript in a browser can initiate HTTP requests to other resources. This has become known as "AJAX": Asynchronos JavaScript and XML.

You can see this in action by visiting this AJAX Books Example.

XML? #

In the early days of the internet XML was a common, flexible, text-based data interchange format. However it is verbose, and its flexibility leads to it being somewhat difficult to parse. As Javascript became more popular, the object notation it pioneered became used as a data format separate from the language itself. This becane simple "JSON": JavaScript Object Notation. Without going into the specifics of each format, just know that even though the JavaScript way to make HTTP requests is XMLHttpRequest, it can be used to handle any format of data, and not just XML.

HTML Page #

We'll need a basic HTML page to start with.

 1<!doctype html>
 2<html>
 3<head>
 4  <title>examples/ajax-books.html</title>
 5  <script src="ajax1.js"></script>
 6  <link rel="stylesheet" type="text/css" href="ajax.css" />
 7</head>
 8<body>
 9  <h1>A Sample AJAX Page</h1>
10  <section id="books">
11    <table id="book-table">
12      <tr>
13        <th>Title</th>
14        <th>Author</th>
15      </tr>
16    </table>
17    
18    <button id="book-button">Get Books</button>
19  </section>
20</body>
21</html>

This gives us some structure to deal with from the JavaScript side of things. The JavaScript code is loaded on line 5 with the <script> element. This is a relative URL, so the browser requests ajax1.js from the same folder that the .html file is in.

JavaScript with XMLHttpRequest #

We'll go through the contents of ajax1.js in small chunks. You can see the full file here. The code snippets with line numbers below are directly pulled from the JavaScript file. Other code blocks are just additional examples.

5window.addEventListener('load', function() {
6  // Get a reference to our button
7  var button = document.getElementById('book-button');
8  button.addEventListener('click', getBooks);
9});

This uses the window.addEventListener method to attach a new listener to the load event. Whenever the window's load event completes, the second argument, containing the callback function, will be called. Recall that the basic format for this method is:

window.addEventListener('eventName', callbackFunction)

In our example, the eventName is "load", and the callbackFunction is an inline function defined right there:

function() {
  var button = document.getElementById('book-button');
  button.addEventListener('click', getBooks);
}

This callback function uses document.getElementById to get a reference to the <button id="book-button">Get Books</button> element in our HTML file. Because we added an id attribute to the element's openning tag, it allows us to easily get access to it from JavaScript.

Once we have our button element, we'll add an event listener to that element too. In this case we'll listen for the 'click' event, and pass in the getBooks function declaration which is defined next.

16function getBooks(event) {
17  console.log('Button Pressed');
18  
19  // Create a new XMLHttpRequest Object
20  var req = new XMLHttpRequest();
21  
22  // Create a callback function when the State of the Connection changes
23  req.onreadystatechange = function() {
24    if (req.readyState == 4)       // state of 4 is 'done'. The request has completed
25    {
26      loadBooks(req.responseText); // The .responseText property of the request object
27    } else {                       // contains the Text returned from the request.
28      console.log(req.readyState);
29    }
30  };
31  
32  // Set up our HTTP Request
33  var booksAPI = "https://csc346picturegram.test.apps.uits.arizona.edu/Prod/books";
34  req.open('GET', booksAPI, true);  // the 3rd parameter denotes if we want the request to
35                                    // be asynchronous or not. Here we do, so its true
36  
37  // Finally initiate the request
38  req.send();
39}

Here we have some console.log calls to print out progress as we go. This is useful for development and debugging, but remember to take these lines out of production code! For this class, it's fine to leave in any log calls like this.

Next, we create a new instance of the XMLHttpRequest() class, and store it in our req object.

With our new req object, we create a new function expression, and assign it to the .onreadystatechange property of the req object.

readystatechange event #

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readystatechange_event

The readystatechange event is fired every time the state of the XMLHttpRequest object changes. An HTTP request created and managed by the XMLHttpRequest object goes through various states:

State Value Description
unsent 0 The object has been constructed.
opened 1 The open() method has been successfully invoked. During this state request headers can be set using setRequestHeader() and the fetch can be initiated using the send() method.
headers received 2 All redirects (if any) have been followed and all headers of a response have been received.
loading 3 The response body is being received.
done 4 The data transfer has been completed or something went wrong during the transfer (e.g., infinite redirects).

It's very rare that we'll actually care about anything other then the done state. So line 24 tests for a value of 4, which is done, and only does something when that's the case. For illustrative purposes though, we'll print out the other states.

What we're basically doing here is saying "when this request has completed, call the loadBooks function, and pass the req.responseText to it" on line 26.

Note that nothing has actually happened yet. We're still just configuring our req object. Next we have to initialize the request by calling the open method.

open #

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/open

The method name .open is a bit misleading. It does not actually create the socket at this point. It just sets up how and where to connect.

req.open('GET', booksAPI, true)

Similar to other ways we've created HTTP requests, here we specify the HTTP verb, and the URL to access. The third parameter tells the browser to execute this call asynchronously, vs sychronously. Because JavaScript operates on an event loop, it is possible to tell JavaScript to block execution of code until this request completes, or to continue on and let the callback functions handle changes. We almost always want to do things asychronously for a better user experience, even though this makes our job as develoeprs a little harder.

send #

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/send

Finally, we have everything in place to send our request.

We've set up where we want to send the request do in the open method, and we've defined how we will handle the reponse with the readystatechange event handler. You can see how this differs from some other programming languages, where we usually assign the result of a request to some variable, such as with the python requests module. A similar API call with python would look something like this:

booksResult = requests.get(booksAPI)

However with the asychronous JavaScript runtime, instead of blocking execution at the send call, JavaScript immediately returns control to the event loop. The event loop then tracks the progress of the request, and updates the readyState of the req object, calling our registered callback function at each change. When the request completes, and the callback method is finally called with a state value of 4, we know our call has completed (hopefully successfully) and we can access our expected data and do something with it.

Javascript and the fetch API #

While XMLHttpRequest works fine, as you can see, it's a little clunky. As you're probably thinking, this is a bit complicated to explain to new developers. Over the years many libaries attempted to abstract away this complexity: jQuery, Prototype, axios, and others.

The web standards community worked on this for some time, and around 2018, the new fetch API became available in browsers. Today all modern browser support this.

The fetch API is a Promise based API. This means it avoids the callback nesting problem. It also does a much better job at handling failures. Probably most importantly, it's just simpler to write and read.

Consider this alternate form of the getBooks() function above:

function getBooks(event) {
  console.log('Button Pressed');
  fetch("https://csc346picturegram.test.apps.uits.arizona.edu/Prod/books")
    .then((response) => response.text())
    .then((responseText) => loadBooks(responseText))
}

That's it! This acomplishes the same thing as the previous version, but is much more consice and easy to follow. The fetch() function itself has one required argument, the URL to connect with. There's a second optional argument to pass in configuration options. By default the HTTP method will be GET, since that is by far the most used method. If you need to use a different HTTP method, or pass along additional headers, those would all go into the second options argument.

Updating the Page #

Regardless of which method is used to retrieve data from the API call, we now need to do something with that data.