Working with an API | Bypassing the same-origin policy

Today we’re going to look at the basics of accessing an API and using the data we receive from it. Using both Vanilla (Standard) JavaScript and then jQuery we’re going to generate a random Pokémon using the ‘pokéapi’.

HTML:


<!doctype html>
<html lang="en">
    <head>
        <title>Random Pokemon Generator</title>
        <link rel="shortcut icon" type="image/ico" href="/favicon.ico" />
        <!-- Styles -->
        <link href='http://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>

        <link href="assets/css/style.min.css" rel="stylesheet" type="text/css" media="all"/>

    </head>
    <body>

        <header>
          <h1>Random Pokemon Generator</h1>
        </header>

        <main>
          <div class="pokemon" >
            <h2 class="name">
              <span>Name: </span>
              <span ></span>
            </h2>

            <h2 class="species">
              <span>Type: </span>
              <span ></span>
            </h2>

            <div class="sprite">
                <img src="" >
            </div>

            <div class="abilities">
              <h3>Abilities</h3>
              <ul >
                <li>Ability One</li>
                <li>Ability Two</li>
                <li>Ability Three</li>
                <li>Ability Four</li>
              </ul>
            </div>

            <button  role="button">Generate!</button>
          </div>
        </main>

        <script src="assets/scripts/index.min.js"></script>
    </body>
</html>


CSS:


body {
  background-color: #FFFFFF;
  font-family: "Open Sans", sans-serif;
  width: 100%; }

header {
  width: 100%; }

h1 {
  text-align: center; }

h2 {
  font-size: 1.4em; }

main {
  max-width: 600px;
  margin: 0 auto; }

img {
  display: block;
  margin: 0 auto; }

.pokemon {
  background-color: #E3E3E3;
  border: 4px solid #000000; }

.name,
.species {
  text-align: center; }

.sprite {
  height: 100px; }

.abilities {
  margin: 30px 25px;
  height: 160px; }

.abilities h3 {
  padding-left: 60px;
  padding-bottom: 5px;
  border-bottom: 1px solid #999999; }

.abilities ul {
  list-style-type: none;
  margin-left: 15px; }

.abilities li {
  padding: 5px; }

button {
  background-color: #EA0041;
  color: #FFFFFF;
  width: 200px;
  height: 50px;
  border: 1px solid black;
  box-shadow: none;
  margin: 50px auto;
  display: block; }


JavaScript:


// There are 778 pokemon on the database
// This function gets a random number between 0 and 778
function randomIntFromInterval(min,max){
    return Math.floor( Math.random() * (max - min + 1) + min);
};

// Connect to the API
function generate(url, callback) {
    var callbackName = 'jsonp_callback_' + Math.round(100000 * Math.random());
    var pokeurl = url + randomIntFromInterval(0,718).toString() + "/";
    window[callbackName] = function(data) {
        delete window[callbackName];
        document.body.removeChild(script);
        callback(data);
    };
    var script = document.createElement('script');
    script.src = pokeurl + (url.indexOf('?') >= 0 ? '&' : '?') + 'callback=' + callbackName;
    document.body.appendChild(script);
}

// Make a randomly generated pokemon by running the generate function several times

function makeAPokemon() {
    // Use the standard Pokemon URL and output its name
    generate('http://pokeapi.co/api/v1/pokemon/', function(data) {
       // Insert the name of the Pokemon into the "name" span
       document.getElementById("name").innerHTML = data.name;
    });

    // Use the standard Pokemon URL and output its types
    generate("http://pokeapi.co/api/v1/pokemon/", function(data) {
        var types = "";
        // Loop over all the types contained in an array
        for (var i = 0; i < data.types.length; i++) {
            // Set the current type we will add to the "types" span
            var typetoAdd = (data.types[i].name);
            // Capitalise the first letter of the current ability
            typetoAdd = typetoAdd.charAt(0).toUpperCase() + typetoAdd.slice(1, (typetoAdd.length));
            // Append the current type to the overall "types" variable
            types += typetoAdd + " ";
        }
        // Insert each type the pokemon is into the "types" span
        document.getElementById("types").innerHTML = types;
    });

    // Get a list of abilities and add them to the abilities ID
    generate("http://pokeapi.co/api/v1/pokemon/", function(data) {
        var abilities = "";
        // Loop over all the types contained in the abilities array received from the pokeapi
        for (var i = 0; i < data.abilities.length; i++) {
            // Set the current ability we will add to the "abilities" span
            var abilityToAdd = (data.abilities[i].name);
            // Capitalise the first letter of the current ability
            abilityToAdd = abilityToAdd.charAt(0).toUpperCase() + abilityToAdd.slice(1, (abilityToAdd.length));
            // Append the current ability to the overall "abilities" variable
            abilities += "<li>" + abilityToAdd + "</li>";
        }
        // Insert each ability the pokemon has into the "abilities" span
        document.getElementById("abilities").innerHTML = abilities;
    });

    // Run the generate function again, this time on the sprite url
    generate("http://pokeapi.co/api/v1/sprite/", function(data) {
        // Set the received sprite image as the img src for the main image in the HTML
        document.getElementById("sprite").src = "http://pokeapi.co" + data.image;
    });
}

var button = document.getElementById("generate");
// When the button is clicked, run the makeAPokemon() function
button.addEventListener("click", makeAPokemon);

// Run the makeAPokemon() function once when the page loads
makeAPokemon();


Check out the working codepen here: http://codepen.io/Charlotteis/pen/vqdls


Wow. Full disclosure, writing that Vanilla JavaScript was simple. However; it was only simple after 2-3 hours of trying, discovering the murky waters of same-origin policies and cross origin resources before finally finding a very dirty solution (in my opinion) in Stack Overflow.

Let me explain. I need to make a request to the pokéapi, ask it to give me its data and then manipulate and show that data within my random Pokémon generator.

To fetch data you can use XMLHttpRequest(), In short; you create a new XMLHttpRequest() object, send the request to the pokéapi servers in order to GET data from a pokéapi url and execute a function to manipulate the data if it is returned successfully.

Looking at the code above, you’ll notice I didn’t do this, because I wasn’t allowed. There is something called the “same-origin policy”.

The policy permits scripts running on pages originating from the same site to access each other’s DOM with no specific restrictions, but prevents access to DOM on different sites.

I am allowed to fetch data from different files on my website, but I am not freely allowed to fetch the data from other websites and servers.

Further:

The same-origin policy also applies to XMLHttpRequests unless the server provides a Access-Control-Allow-Origin (CORS) header.

Unfortunately for me, the pokéapi site does not seem to provide a CORS header.

So, in order to fetch the JSON from the pokéapi I need to retrieve the data via a script node injected into my document body. It’s ugly and I wasn’t able to write it myself at this stage (thanks to Paul Draper at Stack Overflow for writing it). The rest of the JavaScript was fairly straight forward, I’m just randomly generating Pokemon data from the API and inserting it into specific parts of the DOM.


jQuery:


// There are 778 pokemon on the database
// This function allows us to generate a random number between two limits
function randomIntFromInterval(min,max){
    return Math.floor( Math.random() * (max - min + 1) + min);
};

// A more specific number between 0 and the number of poke on database
function randPokemon() {
    return randomIntFromInterval(0, 718).toString();
}

// Fetch a random pokemon name
function generateName(urlinput, id) {
    var generateurl = "http://pokeapi.co/api/v1/" + urlinput + randPokemon();

    $.ajax({
        type: "GET",
        url: generateurl,
        // Set the data to fetch as jsonp to avoid same-origin policy
        dataType: "jsonp",
        async: true,
        success: function(data){
            // If the ajax call is successful, add the name to the "name" span
            $(id).text(data.name);
        }
    });
}

// Fetch random pokemon types
function generateTypes(urlinput, id) {
    var generateurl = "http://pokeapi.co/api/v1/" + urlinput + randPokemon()

    $.ajax({
        type: "GET",
        url: generateurl,
        dataType: "jsonp",
        async: true,
        success: function(data){
            var types = "";
            // Loop over all the types contained in an array
            for (var i = 0; i < data.types.length; i++) {
                // Set the current type we will add to the "types" span
                var typetoAdd = (data.types[i].name);
                // Capitalise the first letter of the current ability
                typetoAdd = typetoAdd.charAt(0).toUpperCase() + typetoAdd.slice(1, (typetoAdd.length));
                // Append the current type to the overall "types" variable
                types += typetoAdd + " ";
            }
            // Insert each type the pokemon is into the "types" span
            $(id).text(types);
        }
    });
}

// Fetch random pokemon abilities
function generateAbilities(urlinput, id) {
    var generateurl = "http://pokeapi.co/api/v1/" + urlinput + randPokemon()

    $.ajax({
        type: "GET",
        url: generateurl,
        dataType: "jsonp",
        async: true,
        success: function(data){
            var abilities = "";
            // Loop over all the abilities
            for (var i = 0; i < data.abilities.length; i++) {
                // Set the current ability we will add to the "abilities" span
                var abilityToAdd = (data.abilities[i].name);
                // Capitalise the first letter of the current ability
                abilityToAdd = abilityToAdd.charAt(0).toUpperCase() + abilityToAdd.slice(1, (abilityToAdd.length));
                // Append the current ability to the overall "abilities" variable
                abilities += "<li>" + abilityToAdd + "</li>";
            }
            // Insert abilities to "abilities" span
            $(id).html(abilities);
        }
    });
}

// Fetch a random pokemon image
function generateSprite(urlinput, id) {
    var generateurl = "http://pokeapi.co/api/v1/" + urlinput + randPokemon()

    $.ajax({
        type: "GET",
        url: generateurl,
        dataType: "jsonp",
        async: true,
        success: function(data){
            var href = "http://pokeapi.co" + data.image;
            // Add random image source to the sprite image source
            $(id).attr("src", href);
        }
    });
}

// Use all generate functions together to make a new random pokemon!
function makeAPokemon() {
    generateName("pokemon/", "#name");
    generateTypes("pokemon/", "#types");
    generateAbilities("pokemon/", "#abilities")
    generateSprite("sprite/", "#sprite")
}

// If the generate button is clicked, call the makeAPokemon() function
$("#generate").on("click", makeAPokemon);

// Call the makeAPokemon() function once initial page load
makeAPokemon();

Woking codepen: http://codepen.io/Charlotteis/pen/LuroC


jQuery definitely wins this round . We’re using actual requests without problem, with no strange bypasses to fetch JSONP from the pokéapi.

I do want to point out that if you were making requests to a file elsewhere on your own website, rather than an external server (therefore sticking to the same-origin policy), things would be a lot easier in the Vanilla JavaScript. So much so that I’d use this over jQuery.


Same-Origin JavaScript:


request = new XMLHttpRequest();

// Third parameter set to true or false depending on whether you want the request to be done synchronously (false) where the website waits until the data is received before moving onto its next task or asynchronously (true) where it doesn't.

request.open('GET', '/pokemononmyserver/', false);

// Once the request has been successful, do the following...
request.onload = function() {
  if (request.status >= 200 && request.status < 400){

    // Turn whatever data is received into JSON
    data = JSON.parse(request.responseText);

    // Log data to the console
    console.log(data);

  } else {
    console.log("Obscure error message");

  }
};

// Send the request in order to retrieve the data
request.send();

So there you have it. Although it was filled with frustration, It was really interesting to work with same-origin policies in mind and overcome the awfulness of working with it in Vanilla JS.

Of course it very much depends on the time constraints of your project, but loading in jQuery just for a couple of ajax requests may not be what you want to do. Having said that, working with cross-origin data from servers without a CORS header is dreadful and I can see myself using jQuery for that use case in the future.

Check out my Pokemon Generator and feel free to submit an issue/pull request on the GitHub repository if you’d do it differently.

• Happy Birthday Babo !!

So yeah, here I am, late as usual, but I’m always late at everything anyway, even when it came to liking you. (Do I even make sense ??!) During Ta-Dah I didn’t really pay attention to you because a cheetos was stuck in my eyes.. Then it was a certain commander. But I still thought that you were cute, yelling like an idiot alone on the roof. But then during the Power era everything changed and I don’t even know why, maybe because I started shipping DaeJae at that time ? (Am I THAT lame ??!) Even if it’s the reason, you started growing on me and then I just knew it was you. I found myself looking forward your teaser pictures, your parts in MVs or appearances in other videos more than for the others. I always smiled more and I eventually asked myself why I didn’t feel like that at the beginning. I guess I was just blinded by the whole group and tried to focus on knowing these aliens instead of having a bias. (I wasn’t THAT fond of the cheetos and the commander.) Anyway I re-watched Ta-Dah and I fell in love some more with your fluffy polite being. I won’t start writing how much you’re amazing for following your dreams and working as hard as you do, because I’m bad at it. You’re just an amazing person with a such big big heart, caring for the fans with the beautiful smile of yours, even when some were giving you a hard time because of stupid reasons. You sure have an amazing voice, but more important, you’re just being yourself, even if it means laughing like an idiot and jumping around while stomping on the floor. I just love seeing that because oddly it just makes me happy to see you happy too. Never change and never forget that you have tons of fans loving you around the world ! Happy Birthday Youngjae ♥

Text
Photo
Quote
Link
Chat
Audio
Video