James Ononiwu
working with APIs in javascript
8 min readNov 8, 2019

--

WORKING WITH NASA OPEN APIs

NASA(national aeronautic and space administration) houses a lot of data about its space explorations. this data is made available to developers through APIs(application programming interface). APIs are basically the building blocks of the web. they are set of routines and protocols for building software applications.

How APIs connects every part together

The complete source code for the app we are about to build can be cloned or downloaded from my GitHub repo below, but if you are totally new to working with APIs in JavaScript then i advice you follow along by typing the code by yourself. so lets proceed.

https://github.com/jamesbright/eyes-on-mars

first you need to head over to the official NASA APIs store and create a new API key.

https://api.nasa.gov/

Enter your details in the spaces provided and click signup, you will receive your key on the next page after this. but not to worry if you can’t go through the process now, i already have an API key we will be using to build our app. we will be using Bootstrap for styling, if you are new to bootstrap framework you can read more about it here https://getbootstrap.com/docs/4.3/getting-started/introduction/ .

Next create and implement the following files using vs code or any code editor of your choice as follows.

index.html

explanation: The index file contains our navigation bar. in the navbar we have the search input, date input and dropdown of camera names we will be implimenting them shortly.

<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Martian Tour</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1 ">
<ul class="nav navbar-nav navbar-right"><li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Camera<span class="caret"></span></a><ul class="dropdown-menu" role="menu" data-camera ><li><a href="#" title="Chemistry and Camera Complex">CHEMCAM</a></li><li><a href="#" title="Front Hazard Avoidance Camera">FHAZ</a></li><li><a href="#" title="Mars Descent Image">MARDI</a></li><li><a href="#" title="Mast Camera">MAST</a></li><li><a href="#" title="Mars Hand Lens Image">MAHLI</a></li><li><a href="#" title="Navigation Camer">NAVCAM</a></li><li><a href="#" title="Rear Hazard Avoidance Camera">RHAZ</a></li></ul></li></ul>
<form class="navbar-form navbar-left" role="search" onsubmit="searchSol()">
<div class="form-group">
<input type="text" id="search-text" class="form-control" placeholder="Enter Sol">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
<input type="date" class="navbar-right" onchange="searchByDate(this.value)" />
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>

Next after the navbar we have the div with an id set to contents. we will be populating this div with our data from the API soon.

<div class="container-fluid"><div class="wrapper"><div class="row no-pad" id="contents"></div></div></div>

after the content div we have our loading indicator to give the user some info about what is happening instead of leaving the page blank while data is being loaded.

<img id="loading" src="images/loading.gif" />

The loading.gif and other files are on the github repo for this app, so go ahead and clone it.

Next we will move to writing the JavaScript code to fetch data from the API. first of all head over to https://api.nasa.gov/ and click on browse APIs, then navigate to the mars rover photos section as shown below.

Go through the query parameters for some seconds to understand how it works. lets begin by creating our script.js file with the implementation inside.

script.js

Explanation: at the beginning of the file we have our declarations with comments to explain their use as shown below.

let posY = 24; //mouse pointer x position
let posX = 12; // mouse pointer y position

let diff = 25; //diff as a variable for position offset
// bootstrap row class
const rowElem = document.querySelector('.row');
// randomised search by sol
let mSol = Math.floor(Math.random() * 1000) + 1;
// api key
const apiKey = 'sM9dReXlARhfcp9ctZGxUt8wItACbqJTLMCW3YiI';

the posY and posX variables sets the initial position of the mouse pointer relative to the images fetched.in the animateScript function we used a third party JavaScript library d3.js . this library is responsible for getting the x and y coordinates of the mouse pointer positioned on the screen as the user moves it, when we have the coordinates we update the values of the posX and posY variables to the new coordinates.

let coordinates = [0, 0];
d3.select(event.target) // Selects the 'html' element
.on('mousemove', function () {
coordinates = d3.mouse(this); // Gets the mouse coordinates
console.log(coordinates);
posX = Math.round(coordinates[0]);
posY = Math.round(coordinates[1]);

this new pointer position value is used to make the part of the image the pointer is currently at visible. this is because each image is too large and to fit all the retrieved images on the same screen without loosing some part of it, there is need to make them smaller fixed size then expose hidden parts of the image as the user moves to that area. this was achieved by setting the css backgroundPosition property of the image to the values of posX and posY variables. an additional diff variable is used as a check to determine when the user have navigated to the edges of the the image and set the visible part accordingly.

if (posX < 1398 || posY < 934) {
posX = posX + diff;
posY = posY + diff;
}
//we increment the position each time
if (posX > 368 || posY == 367) {
posX = 0;
posY = 0;
}

the complete animateScript function is shown below.

const animateScript = () => {
let coordinates = [0, 0];
d3.select(event.target) // Selects the 'html' element
.on('mousemove', function () {
coordinates = d3.mouse(this); // Gets the mouse coordinates
console.log(coordinates);
posX = Math.round(coordinates[0]);
posY = Math.round(coordinates[1]);
const image = event.target;image.style.backgroundPosition =
`-${posX}px -${posY}px`;
if (posX < 1398 || posY < 934) {posX = posX + diff;
posY = posY + diff;
}
//we increment the position each time
if (posX > 368 || posY == 367) {
posX = 0;
posY = 0;
}
});
} //end of animateScript

Next getPhotos function takes in an api url as an argument. this is where we make our API call to get the ROVER images data from the API end point.if the fetch operation is successful we check if previous data exist in the view and remove it by toggling the visible and hidden css class, then we iterate through the retrieved data object and create html elements that will hold our populated data, we also bind the images to a mouseover event listener which will fire the animateScript function, these elements are then appended to the final element that serves as the container to display the data to the user.

const getPhotos = (api) => {//remove data-holder elements if it already exists
$('#contents').empty();
$('#loading').removeClass('hidden');
$('#loading').addClass('visible');
fetch(api)
.then(response => response.json())
.then(data => {
console.log(data.photos.length);
if (data.photos.length > 0) {
data.photos.forEach(element => {
console.log(element);// create data-holder elements to hold the data
const containerElem = document.createElement('div');
const photoElem = document.createElement('div');
const detailElem = document.createElement('p');
containerElem.className = "col-sm-4 col-md-4 col-lg-4 col-xs-offeset-1 mrover-data-container";
containerElem.id = "mrover-photo-container";
photoElem.className = "mrover-photo-details lazy";
photoElem.id = "image";
detailElem.textContent = "";
//bind mouse events
if (photoElem.addEventListener) { // all browsers except IE before version 9
photoElem.addEventListener("mouseover", animateScript, false);
} else {
if (photoElem.attachEvent) { // IE before version 9
photoElem.addEventListener("mouseover", animateScript);
}
}
photoElem.appendChild(detailElem);
containerElem.appendChild(photoElem);
rowElem.appendChild(containerElem);
detailElem.innerText = `Camera: ${element.camera.name}
Earth Date: ${element.earth_date}
Rover: ${element.rover.name}
Sol: ${element.sol}`;
$('#loading').removeClass('visible');
$('#loading').addClass('hidden');
});
} else {
$('#loading').src = "images/Error.png";
}
})
.catch(error => {
console.log(error);
});
} // end getPhotos()

getPhotos function is called from different places in the script. when the document finised loading.

//get data when the document gets ready
$(document).ready(function () {
const api = `https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=${mSol}&page=2&api_key=${apiKey}`;
getPhotos(api);
});

when we search the API endpoint by sol.a sol is a martian solar day. visit :

for more info. the search form and its implementation is shown below

<form class="navbar-form navbar-left" role="search" onsubmit="searchSol()"><div class="form-group"><input type="text" id="search-text" class="form-control" placeholder="Enter Sol"></div><button type="submit" class="btn btn-default">Submit</button></form>// search by sol 
const searchSol = () => {
event.preventDefault();
const value = document.querySelector('#search-text').value;
const api = `https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=${value}&api_key=${apiKey}`;
console.log(value);
getPhotos(api);
}

searching the endpoint by name of camera used by the rover for taking the images.

<ul class="nav navbar-nav navbar-right"><li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Camera<span class="caret"></span></a><ul class="dropdown-menu" role="menu" data-camera ><li><a href="#" title="Chemistry and Camera Complex">CHEMCAM</a></li><li><a href="#" title="Front Hazard Avoidance Camera">FHAZ</a></li><li><a href="#" title="Mars Descent Image">MARDI</a></li><li><a href="#" title="Mast Camera">MAST</a></li><li><a href="#" title="Mars Hand Lens Image">MAHLI</a></li><li><a href="#" title="Navigation Camer">NAVCAM</a></li><li><a href="#" title="Rear Hazard Avoidance Camera">RHAZ</a></li></ul></li></ul>// bind the search by camera
const cameras = document.querySelectorAll('[data-camera] a');
cameras.forEach(camera => {
camera.addEventListener('click', () => {
event.preventDefault();
const value = event.target.textContent;
console.log(value);
const api = `https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=${mSol}&camera=${value}&api_key=${apiKey}`;
getPhotos(api);
});
});

And finally searching by the earth date of which the image was taken.

<input type="date" class="navbar-right" onchange="searchByDate(this.value)"  />const searchByDate = (value) => {
console.log(value);
const api = `https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?earth_date=${value}&api_key=${apiKey}`;
getPhotos(api);
}

These functions use the passed in value from the html inputs to construct a url query to the API using the apiKey for authentication.

Now lets move on to the CSS styling.

style.css

we need to pay attention to certain section of the styling which enables zooming of the current image when the mouse pointer is hoverd or placed on top of it. thereby giving more details about the image.

.mrover-data-container:hover #image,
.mrover-data-container:focus #image {
transform: scale(1.5);
z-index: 100;
}
final result

we have come to the end, and thank you for reading. More content on Javascript and javascript frameworks will be available soon.

--

--