15 Dec 2018

Code Companion #11: AJAX

Introduction

In this tutorial we’ll be building off of the previous tutorial updating our form to make our first HTTP request.

Everything up to this point has been relatively “static”. We’ve been working on the client side of the web (refer back to the “Client Server Model” section in Code Companion #1 if necessary). All of the data in our previous tutorials was either hard-coded or provided by user inputs. We now want to bring in the server side of the client server model by interacting with an API.

The HTTP request we’ll create will be made to the Pokeapi to retrieve additional details about the Pokemon that was entered into the form. We’ll then extract the image of the Pokemon from these details and add it to the DOM.

This HTTP request will be made using AJAX which stands for Asynchronous JavaScript And XML. One of the primary characteristics of AJAX is its asynchronous nature, meaning we will be able to make an HTTP request for the Pokemon image, receive the response from Pokeapi, and update the page without the user having to refresh the page.

Setup

Since we’ll be building off of our previous code we’re going to start by “cloning” or copying your code from GitHub assuming you pushed it to GitHub at the end of the tutorial.

First, find the URL for your repository by navigating to your repository page and clicking the green “Clone or download” button.

Repository git url

Then you can clone your project with the following command updating the URL to your own Git repository. This gives us a copy of our old code so that we can make add new features while leaving our forms project intact.

git clone https://github.com/adammorganshow/forms.git ajax

The ajax at the end specifies the directory name to place this project into saving us the steps of mkdir ajax and cd ajax.

If you didn’t push your project to GitHub and you don’t have your old code from Code Companion #10, feel free to use my repository which is listed in the git clone command above.

Update addToPokedex

The first thing we’ll want to do is update our previous unordered list to a div.

<div id="pokemon-list"></div>

In the previous tutorial, we were simply adding the names of each Pokemon to the DOM which is a great use for an unordered list. But going forward, we’re going to have more details for each Pokemon and an unordered list is a bit too simplistic for our needs. Now our new div with the id pokemon-list will serve as the container for the Pokemon that are submitted using the form.

Since we’ve updated the ul to a div, we’ll also need to make a few small edits to our addToPokedex function.

First, we’ll remove the variable declaration and initialization for listItem which created a new li element for our unordered list which we just updated to a div. We’ll also remove the last two lines of our function which set the innerHTML of listItem and appended it to pokemon-list.

At this point your addToPokedex function should look like this:

function addToPokedex(event) {
  event.preventDefault();
  let name = event.target.pokemon.value;
  let description = event.target.description.value;

  let list = document.getElementById('pokemon-list');
}

Now we want to update this function to add the name and description back to pokemon-list with an updated HTML structure.

To do this, we’ll be creating a container (div) for each Pokemon with a p inside of it containing the Pokemon’s name and description. Then, we’ll add this container to pokemon-list. (Later on in this tutorial, we’ll also add the Pokemon image to this container (div)).

function addToPokedex(event) {
  event.preventDefault();
  let name = event.target.pokemon.value;
  let description = event.target.description.value;

  let pokemonContainer = document.createElement('div');
  pokemonContainer.id = name.toLowerCase();
  let pokemonContent = document.createElement('p');
  pokemonContent.innerHTML = `${name} - ${description}`;
  pokemonContainer.appendChild(pokemonContent);

  let list = document.getElementById('pokemon-list');
  list.appendChild(pokemonContainer);
}

First we create a new div element and set it to the pokemonContainer variable. Then we set the id of that div to the name of the Pokemon entered into the form using the .toLowerCase() method to lowercase the name (it’s common convention to avoid uppercase letters in CSS ids and classes).

Then we create a p element, set it to the pokemonContent variable, and set its innerHTML to a template literal string containing the Pokemon’s name and description.

Now that our two elements have been created we then add pokemonContent to pokemonContainer using .appendChild(). Finally, we add pokemonContainer to pokemon-list leveraging our local DOM object list.

At this point you can go back to your browser, refresh, and verify everything is still working. The only difference will be the styling. Since we’re no longer using an unordered list, there won’t be bullets for each Pokemon entered—they’re now p tags.

DOM Refactor

Anatomy of Ajax

Now that we’ve updated our HTML for our new structure with the appropriate changes in addToPokedex we can move on to AJAX which we’ll be using to make HTTP requests.

Let’s take a look at AJAX requests breaking it down into the various pieces which serve as the anatomy of an AJAX request.

XMLHttpRequest

To make an AJAX request we’ll be leveraging XMLHttpRequest to create an XMLHttpRequest, or XHR, object. A simple example of this is shown below.

let httpRequest = new XMLHttpRequest();

Similar to other constructor functions such as Date, we create this object using the new keyword setting it to a variable that we can interact with.

Response handler

Once we make a request using our new XHR object, we’ll eventually receive a response. To handle this response we’ll be using the onreadystatechange property.

let httpRequest = new XMLHttpRequest();

httpRequest.onreadystatechange = function() {
  // Process the response here
};

As you can see, we set the onreadystatechange property to a function. Within that function is where we write our code to process the response we receive from our HTTP request.

Request options

At this point we’ve declared what happens when we receive a response from our request but now we need to actually make our request.

First, we initialize our request using the .open() method.

let httpRequest = new XMLHttpRequest();
httpRequest.open('GET', 'http://someurl.com');

httpRequest.onreadystatechange = function() {
  // Process the response here
};

The open method takes two parameters: the HTTP request method and the URL to send the request to.

In the above example we’ve provided the request method GET which simply retrieves data from the requested resource our URL. In our case, the URL is a made up URL, http://someurl.com. (We’ll get to our Pokeapi example shortly.)

There are other HTTP request methods such as PUT, POST, PATCH, and DELETE which you can read about here. We’ll take a closer look at those in future tutorials. For now, we’ll be working with GET since it’s the simplest one of them all and usually the first HTTP request you create in your career as a web developer.

Now that we’ve specified our request method and the URL for our request, we can make the request using the .send() method.

let httpRequest = new XMLHttpRequest();
httpRequest.open('GET', 'http://someurl.com');
httpRequest.send();

httpRequest.onreadystatechange = function() {
  // Process the response here
};

This is the basic structure of AJAX requests.

  1. Create an XHR object using XMLHttpRequest
  2. Create a response handler using onreadystatechange
  3. Specify the request method and URL using .open()
  4. Make the request using .send()

Fetch Pokemon (AJAX)

Now that we’ve seen how to create AJAX requests, we can work towards creating one for our request to Pokeapi. We’ll be creating a separate function for this request so let’s start by outlining our requirements for this functionality.

Requirements

  1. Create a new fetchPokemon function
  2. Call our new fetchPokemon function in the form submit event handler
  3. Get the Pokemon name from the the function parameter which will be passed in from the form submit event handler
  4. Setup the AJAX request
  5. Setup the response handler

We’ll begin with requirement #1 by creating our new function.

function fetchPokemon(event) {
}

With our new function defined, we can now call our new function to fulfill requirement #2.

document.addEventListener('DOMContentLoaded', function() {
  let form = document.getElementById('pokedex');
  form.addEventListener('submit', function() {
    addToPokedex(event);
    fetchPokemon(event);
  });
});

Now that our function is called with the event object as an argument, we can extract the Pokemon name from it in fetchPokemon function for requirement #3.

function fetchPokemon(event) {
  let pokemon = event.target.pokemon.value.toLowerCase();
}

We now have the Pokemon name in our fetchPokemon function. Now we can setup our AJAX request for requirement #4.

function fetchPokemon(event) {
  let pokemon = event.target.pokemon.value.toLowerCase();

  let request = new XMLHttpRequest();
  let url = `https://pokeapi.co/api/v2/pokemon/${pokemon}/`;
  request.open('GET', url);
  request.send();
}

First we create our XHR object. Then we create a url variable set to a template literal string of the Pokeapi endpoint which contains the pokemon variable we created earlier.

We then use .open() to specify our request method, GET, and the URL using our url variable. Finally, we call .send().

Now we can move on to requirement #5 by setting up our response handler.

function fetchPokemon(event) {
  let pokemon = event.target.pokemon.value.toLowerCase();

  let request = new XMLHttpRequest();
  let url = `https://pokeapi.co/api/v2/pokemon/${pokemon}/`;
  request.open('GET', url);
  request.send();

  request.onreadystatechange = function() {
    // Handle response here
  };
}

Add Pokemon image to DOM

At this point we’re now ready to implement the functionality within our response handler. Once again, we’ll break this down into a series of requirements working towards our final goal of adding the Pokemon image from the response to the DOM.

Requirements

  1. Verify the server response was received
  2. Check the response status code of the HTTP response
  3. Handle any errors
  4. Parse the response
  5. Get the image from AJAX response
  6. Get the pokemon name from AJAX response
  7. Create the image element
  8. Add image to DOM

To start we’ll address requirement #1, verifying the response was received. To do this we’ll be checking the readyState property of our XHR object as follows.

Since the remainder of this code is handling the HTTP response within onreadystatechange, I’ll be omitting the rest of the code to keep the code blocks as small as possible.

request.onreadystatechange = function() {
  if (request.readyState === 4) {
    // Request is finished. Response is ready!
  }
};

Here we check that request.readyState is equal to 4 which means that the request has completed.

You can read about the other readyState values here.

Now that we have our conditional for a completed request, we can now check the status code of our response, a success or an error, which will address requirement #2.

request.onreadystatechange = function() {
  if (request.readyState === 4) {
    if (request.status === 200) {
      // Good to go!
    } else {
      // There was a problem.
    }
  }
};

Here we check for a successful response code using the status property. If it’s a 200 or OK response code, we’ve received a successful response. Otherwise, there was an issue which we can address in our else statement.

For now, we’ll simply write a message to the console if we receive any errors to address requirement #3.

request.onreadystatechange = function() {
  if (request.readyState === 4) {
    if (request.status === 200) {
      // Good to go!
    } else {
      console.log('There was a problem with the request.');
    }
  }
};

Now we can move on to requirement #4, parsing the response from our HTTP request.

request.onreadystatechange = function() {
  if (request.readyState === 4) {
    if (request.status === 200) {
      let response = JSON.parse(request.responseText);
      // Add Pokemon image to DOM
    } else {
      console.log('There was a problem with the request.');
    }
  }
};

Here we use JSON.parse() to parse the response from our request in the form of request.responseText which, according to the documentation for responseText, “contains the response to the request as text”. Wrapping that with JSON.parse() converts it to a JavaScript object that we set to response.

These properties are all listed within the documentation for XMLHttpRequest under the properties section. Over time, you may memorize some of these but when you’re just starting out it’s helpful to have documentation open so you know the various properties and methods that are exposed to you.

Now that we have a response object we can interact with, we can begin to get the bits of data we need to add this to the DOM.

First, we’ll get the image path and Pokemon name to address requirements #5 and #6.

request.onreadystatechange = function() {
  if (request.readyState === 4) {
    if (request.status === 200) {
      let response = JSON.parse(request.responseText);

      let path = response.sprites.front_default;
      let name = response.name;
    } else {
      console.log('There was a problem with the request.');
    }
  }
};

Now we can create our img tag setting its src and alt attributes to address requirement #7.

request.onreadystatechange = function() {
  if (request.readyState === 4) {
    if (request.status === 200) {
      let response = JSON.parse(request.responseText);

      let path = response.sprites.front_default;
      let name = response.name;

      let image = document.createElement('img');
      image.src = path;
      image.alt = name;
    } else {
      console.log('There was a problem with the request.');
    }
  }
};

Finally, we can add our new image object to the DOM to fulfill our last requirement.

request.onreadystatechange = function() {
  if (request.readyState === 4) {
    if (request.status === 200) {
      let response = JSON.parse(request.responseText);

      let path = response.sprites.front_default;
      let name = response.name;

      let image = document.createElement('img');
      image.src = path;
      image.alt = name;

      let pokemonListItem = document.getElementById(name.toLowerCase());
      pokemonListItem.appendChild(image);      
    } else {
      console.log('There was a problem with the request.');
    }
  }
};

At this point, you can refresh your browser, complete the form, and you should see the Pokemon image and description added to the DOM.

To clean up our code a bit, we’ll move all of the functionality related to creating the Pokemon img element into its own function.

function placePokemonImage(pokemonData) {
  let path = pokemonData.sprites.front_default;
  let name = pokemonData.name;

  let image = document.createElement('img');
  image.src = path;
  image.alt = name;

  let pokemonListItem = document.getElementById(name.toLowerCase());
  pokemonListItem.appendChild(image); 
}

Note the updated reference to pokemonData instead of response to match our function’s parameter name.

Now we can update our request handler by removing all of the old code to call our placePokemonImage function instead.

request.onreadystatechange = function() {
  if (request.readyState === 4) {
    if (request.status === 200) {
      let response = JSON.parse(request.responseText);
      placePokemonImage(response);     
    } else {
      console.log('There was a problem with the request.');
    }
  }
};

function placePokemonImage(pokemonData) {
  let path = pokemonData.sprites.front_default;
  let name = pokemonData.name;

  let image = document.createElement('img');
  image.src = path;
  image.alt = name;

  let pokemonListItem = document.getElementById(name.toLowerCase());
  pokemonListItem.appendChild(image); 
}

Add loading message

Now that we have added the Pokemon data and image to the DOM, we’ll finish by improving the UX of the form by adding a loading message as the request is being made since it can take a few seconds to complete at times.

First, we’ll create a new function for creating our loading indicator.

function createLoadingIndicator() {
  let loading = document.createElement('p')
  loading.id = 'loading';
  loading.innerHTML = 'Loading...';
  return loading;
}

Note how our function returns the loading object rather than adding it to the DOM directly.

Now we can call this function and add the loading indicator to the DOM within our addToPokedex function.

function addToPokedex(event) {
  event.preventDefault();
  let name = event.target.pokemon.value;
  let description = event.target.description.value;

  let pokemonContainer = document.createElement('div');
  pokemonContainer.id = name.toLowerCase();
  let pokemonContent = document.createElement('p');
  pokemonContent.innerHTML = `${name} - ${description}`;
  pokemonContainer.appendChild(pokemonContent);

  let list = document.getElementById('pokemon-list');
  list.appendChild(pokemonContainer);

  let loadingIndicator = createLoadingIndicator();
  pokemonContainer.appendChild(loadingIndicator);
}

Now when our addToPokedex function is called but before fetchPokemon is called, the loading indicator will be created and added to the DOM.

Refresh and submit the form again and you should notice the loading indicator is created as expected. However, once the request is completed the loading indicator remains.

To fix this, we’ll need to create another function to remove this loading indicator.

Remove loading message

First, we’ll write our function to remove the indicator.

function removeLoadingIndicator() {
  let loading = document.getElementById('loading');
  loading.remove();
}

Now that our function is ready to be called, we have to think about where to call it. We want the loading indicator to be removed once a successful request has been made and completed. A good place for this would be inside our placePokemonImage function which is called only when a successful response is received from our HTTP request.

function placePokemonImage(pokemonData) {
  removeLoadingIndicator();

  let path = pokemonData.sprites.front_default;
  let name = pokemonData.name;

  let image = document.createElement('img');
  image.src = path;
  image.alt = name;

  let pokemonListItem = document.getElementById(name.toLowerCase());
  pokemonListItem.appendChild(image); 
}

Now when we receive a response from our HTTP request, we’ll remove the loading indicator just before adding the Pokemon image to the DOM.

Refresh your browser, complete the form, and you should see the loading indicator appear briefly before being removed once the Pokemon image is added to the view.

Remove loading indicator

Conclusion

You’ve now learned how to make your first HTTP request using AJAX to take a user’s input, send that to a server, and manage a response once it’s sent back. If you’re like me, doing that for the first time felt really cool.

If you’re looking for other APIs to use for development practice there’s a curated list here. To keep things simple on your end, look for an API where the “Auth” column says “No”.

To wrap up, add your latest changes to Git.

git init
git add .

Then add a commit message.

git commit -m "Add AJAX call to form"

Then push these changes up to GitHub.

git push origin master

Exercise

For some extra practice, see if you can update the console.log for request errors to remove the loading indicator and add a message to the DOM for the user to try again.


Before you go...

Feeling stuck copying and pasting from tutorials that don't build realistic applications?

Become an Angular developer by building a real application using open-source libraries with a real API and database.

"The advice and techniques in this book landed me my dream job and literally doubled my salary. Well worth the $99 bucks, so much so that I have been waiting for it to be for sale just to pay the man for the good he has done to my career." -Levi Robertson

CLICK HERE FOR A FREE 8 CHAPTER PREVIEW OF THE ANGULAR TUTORIAL

Learning a front-end framework is hard. "Getting started" tutorials cover the basics but you leave thinking, "Okay, now how do I build something with this?"

The truth is, getting started tutorials aren't all that great for beginners. They're demos to highlight as many features as quickly as possible.

They're great for showing off what a framework can do. They aren't so great for teaching you how to build web apps.

The end result is a basic application that doesn't mimick what it's like building real applications as a front-end developer.

You'll work with a mocked API and database. Application architecture isn't covered. Automated testing is skipped altogether.

Trust me, I've been there. But those days are over.

With The Angular Tutorial, you'll learn how to build applications using a real API and database. You'll leverage 3rd party APIs like Zomato, Google Places, and open-source libraries just as you would in a real job.

The Angular Tutorial assumes you have no previous knowledge of the Angular framework. It starts at the very beginning.

Every piece of code is explained and tested to make you interview ready.

Ready to get started? 👇