The other day I came across London TFL’s public API and realised I could query it for free, which is pretty great.
As a regular user of these bikes, I figured I could build a small web frontend and write some Javascript to get information about the Bike Points I frequently visit. I could get a simple view listing all of them and how busy or empty they are.
Here’s the result:
The complete code is available on Github.
This post shows how it was implemented.
HTML
The HTML is pretty straightforward: it’s an empty page with a simple <div>
of id=root
.
To make the page a bit pretty, I imported Bootstrap, the famous HTML/CSS framework.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TFL Bikes</title>
<link href="https://fonts.googleapis.com/css?family=Dosis:400,700" rel="stylesheet">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
</head>
<body>
<div id="root"></div>
<script src="tfl.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>
</body>
</html>
Javascript
I start with a list of bike terminals I want to check on my page:
///////////////////////
// Variables Setting //
///////////////////////
const terminals = [
"BikePoints_307",
"BikePoints_584",
"BikePoints_261",
"BikePoints_163",
"BikePoints_209"
]
And I’m iterating over them:
window.onload = function(){
var f = (function(){
var xhr = [], i;
for(i = 0; i < terminalsLength; i++){
(function(i){
xhr[i] = new XMLHttpRequest();
url = "https://api.tfl.gov.uk/BikePoint/" + terminals[i];
xhr[i].open("GET", url, true);
xhr[i].onreadystatechange = function(){
if (xhr[i].readyState === 4 && xhr[i].status === 200){
var terminal_data = getTerminalData(xhr[i].responseText)
card_wrapper = getCardWrapper(terminal_data);
row.appendChild(card_wrapper);
}
};
xhr[i].send();
})(i);
}
})();
};
What this code does is looping through each terminal, calling the TFL API to get the current information, and generate an HTML card with the content I want to show.
I had to split the rest of the code into several functions to avoid a small mess in that for
loop. Here’s the entire Javascript file:
// tfl.js
///////////////////////
// Variables Setting //
///////////////////////
const terminals = [
"BikePoints_307",
"BikePoints_584",
"BikePoints_261",
"BikePoints_163",
"BikePoints_209"
]
const app = document.getElementById('root');
const container = document.createElement('div');
container.setAttribute('class', 'container');
app.appendChild(container);
row = document.createElement('div');
row.setAttribute('class', 'row');
container.appendChild(row);
var terminalsLength = terminals.length;
///////////////
// Functions //
///////////////
function search(nameKey, myArray){
for (var i=0; i < myArray.length; i++) {
if (myArray[i].key === nameKey) {
return myArray[i].value;
}
}
}
function getTerminalData(tfl_response){
var response = JSON.parse(tfl_response);
var data = {};
data.terminal_name = response.commonName;
data.id = response.id;
data.lat = response.lat;
data.lon = response.lon;
data.maps = `https://www.google.com/maps/@${data.lat},${data.lon},18z`
data.nb_bikes = search("NbBikes", response.additionalProperties);
data.nb_empty_docks = search("NbEmptyDocks", response.additionalProperties);
data.nb_docks = search("NbDocks", response.additionalProperties);
data.locked = search("Locked", response.additionalProperties);
data.last_updated = response.additionalProperties[0].modified;
data.terminal_used = Number(data.nb_bikes)*100/Number(data.nb_docks);
return data;
}
function getProgressBar(terminal_data){
var bar_color = "";
if(Number(terminal_data.nb_bikes) < 4){
bar_color = "bg-danger";
} else if(Number(terminal_data.nb_bikes) < 6){
bar_color = "bg-warning";
} else {
bar_color = "bg-success";
}
const progress = document.createElement('div');
progress.setAttribute('class', 'progress');
const progress_bar = document.createElement('div');
progress_bar.setAttribute('class', `progress-bar ${bar_color}`);
progress_bar.setAttribute('role', 'progressbar');
progress_bar.setAttribute('style', `width: ${terminal_data.terminal_used}%`);
progress_bar.setAttribute('aria-valuenow', `${terminal_data.nb_bikes}`);
progress_bar.setAttribute('aria-valuemin', '0');
progress_bar.setAttribute('aria-valuemin', `${terminal_data.nb_docks}`);
progress_bar.textContent = `${terminal_data.nb_bikes}`;
progress.appendChild(progress_bar);
return progress;
}
function getCardImg(terminal_data){
card_img = document.createElement('img');
card_img.setAttribute('src', `img/${terminal_data.id}.png`);
card_img.setAttribute('class', 'card-img-top');
card_img.setAttribute('alt', '...');
return card_img;
}
function getMapsLink(terminal_data){
maps_card_body = document.createElement('div');
maps_card_body.setAttribute('class', 'card-body');
card_maps_link = document.createElement('a');
card_maps_link.setAttribute('class', 'card-link');
card_maps_link.setAttribute('href', `${terminal_data.maps}`);
card_maps_link.textContent = "Maps";
maps_card_body.appendChild(card_maps_link);
return maps_card_body;
}
function getCardTitle(terminal_data){
const card_title = document.createElement('h5');
card_title.setAttribute('class', 'card-title');
card_title.textContent = terminal_data.terminal_name;
return card_title;
}
function getLastUpdated(terminal_data){
last_updated = document.createElement('li');
last_updated.setAttribute('class', 'list-group-item');
last_updated.textContent = terminal_data.last_updated;
return last_updated;
}
function getLocked(terminal_data){
locked = document.createElement('li');
locked.setAttribute('class', 'list-group-item');
locked.textContent = `Locked: ${terminal_data.locked}`;
return locked;
}
function getCardList(){
card_list = document.createElement('ul');
card_list.setAttribute('class', 'list-group list-group-flush');
return card_list;
}
function getCardBody(){
card_body = document.createElement('div');
card_body.setAttribute('class', 'card-body');
return card_body;
}
function getCard(){
card = document.createElement('div');
card.setAttribute('class', 'card');
card.setAttribute('style', 'width: 18rem;');
return card;
}
function getCardWrapper(terminal_data){
// Generate Card Image
card_img = getCardImg(terminal_data);
// Generate Progress Bar object
progress_bar = getProgressBar(terminal_data);
// Generate Card Title
card_title = getCardTitle(terminal_data);
// Generate Date Info LI
last_updated = getLastUpdated(terminal_data);
// Generate Locked Info LI
locked = getLocked(terminal_data);
// Generate List UL
card_list = getCardList();
card_list.appendChild(last_updated);
card_list.appendChild(locked);
// Generate Card Body
card_body = getCardBody();
// Add Title and Progress Bar to Card Body
card_body.appendChild(card_img);
card_body.appendChild(card_title);
card_body.appendChild(progress_bar);
// Generate Card Maps Link
card_maps_link = getMapsLink(terminal_data);
// Generate Main Card
card = getCard();
// Add Card Body to Main Card
card.appendChild(card_body);
// Add List UL to Main Card
card.appendChild(card_list);
card.appendChild(card_maps_link);
card_wrapper = document.createElement('div');
card_wrapper.setAttribute('class', 'col-sm-4');
card_wrapper.appendChild(card);
return card_wrapper;
}
/////////////////////////////////////
// Get all cards when loading page //
/////////////////////////////////////
window.onload = function(){
var f = (function(){
var xhr = [], i;
for(i = 0; i < terminalsLength; i++){
(function(i){
xhr[i] = new XMLHttpRequest();
url = "https://api.tfl.gov.uk/BikePoint/" + terminals[i];
xhr[i].open("GET", url, true);
xhr[i].onreadystatechange = function(){
if (xhr[i].readyState === 4 && xhr[i].status === 200){
var terminal_data = getTerminalData(xhr[i].responseText)
card_wrapper = getCardWrapper(terminal_data);
row.appendChild(card_wrapper);
}
};
xhr[i].send();
})(i);
}
})();
};
Images
At the moment, I am using simple screenshots of Google Maps where the terminals are with a 🚲 icon to show the exact location. It would be nice to generate them automatically later.
The images are located in the img
folder. Their name match the terminal ID.
Conclusion
It’s not much (200 lines of JS, no backend needed) but it’s actually a web page I now use pretty much everyday. Maybe it will be useful to you, too! 🚴