Visited Countries Map
Breathtaking view of Pão de Açúcar from Praia Vermelha, Rio de Janeiro, Brazil
See this project: here
We travel not to escape life, but for life not to escape us. – Anonymous
As a delightful way to spend my leisure time, I decided to dabble in creating a small project using HTML, CSS, and JavaScript. This idea was sparked by a desire to easily keep track of all the incredible adventures, encounters, and memories from my travels.
I found that despite having a plethora of pictures, they were dispersed among various platforms and storage devices, making it challenging to consolidate my travel experiences. Additionally, I wanted a straightforward method to share my journey with others and recall the countries I had explored when contemplating future trips.
This led to the development of Visited Countries Map, a user-friendly and complimentary web app designed to help you effortlessly build and personalize your own travel map with just a few clicks. Now, you can visualize your globetrotting escapades and share your unique story with friends, family, and fellow travel enthusiasts.
Tech Stack
- HTML/CSS/JavaScript
- Leaflet (JavaScript library for interactive maps.)
- OpenStreetMap (Open-source map data provider.)
- Replit (Online, browser-based IDE used for development.)
Code
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Visited Countries Map</title>
<link rel="stylesheet" href="style.css">
<script src="script.js" defer></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script>
</head>
<body>
<header>
<section id="title-text">
<h3>Visited Countries Map</h3>
</section>
</header>
<main>
<section id="content-wrapper">
<section id="map-container"></section>
<section id="country-selection">
<section id="country-selection-text">
<h4>Select Visited Countries</h4>
</section>
<div id="country-list"></div>
</section>
</section>
</main>
<footer>
<section id="footer-text">
<p>Created by Tom P-C © 2023</p>
</section>
</footer>
</body>
</html>
CSS
/* Use border-box box-sizing to include padding and border in element width and height */
{
box-sizing: border-box;
}
/* Set default font-family, margin, and padding for body */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
/* Style the header section with background image, padding, and center align text */
header {
background-image: url(BannerFlags.png);
padding: 0.005rem;
text-align: center;
}
/* Style the title text with black font color and yellow background color */
#title-text {
color: black;
background-color: yellow;
}
/* Style the footer text with black font color and yellow background color */
#footer-text {
color: black;
background-color: yellow;
}
/* Use flexbox to display main content in a row and fill the width */
main {
display: flex;
width: 100%;
}
/* Use flexbox to display content wrapper in a row and fill the width, with a margin */
#content-wrapper {
display: flex;
width: 100%;
margin: 0.5rem;
}
/* Style the map container with full width, 80% viewport height, and a 2px solid border */
#map-container {
width: 100%;
height: 80vh;
border: 2px solid #333;
}
/* Style the footer section with bold font weight, background color, black font color, padding, center align text, and background image */
footer {
font-weight: bold;
background-color: #333;
color: black;
padding: 0.05rem;
text-align: center;
background-image: url(BannerFlags.png);
}
/* Style the country selection section with right, top, and bottom borders, flexbox to display items in a column, overflow set to auto, max-height set to 80% viewport height, and padding */
#country-selection {
border-right: 2px solid #333;
border-top: 2px solid #333;
border-bottom: 2px solid #333;
display: flex;
flex-direction: column;
overflow-y: auto;
max-height: 80vh;
padding-right: 1rem;
padding-left: 1rem;
width: 25%;
}
/* Style the country selection text with center align text and yellow background color */
#country-selection-text {
text-align: center;
background-color: yellow;
}
/* Style the country selection checkbox with inline-block display, middle vertical alignment, and margin */
#country-selection input[type="checkbox"] {
display: inline-block;
vertical-align: middle;
margin-right: 0.5rem;
}
/* Style the country selection label with inline-block display, hidden overflow, text-overflow set to ellipsis, and max width set to 90% */
#country-selection label {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
max-width: 90%;
}
/* Style the country selection item with flexbox to align items in a column, margin-bottom, nowrap, and overflow set to hidden */
#country-selection .country-item {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
white-space: nowrap;
overflow: hidden;
}
JavaScript
// Initialize the map
const map = L.map("map-container").setView([20, 0], 2);
// Add a base map from OpenStreetMap
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
// Create an object to store references to each country layer by its name
const countryLayers = {};
// Function to move the selected country item to the top or back to its original position
function moveCountryItemToTop(checkbox) {
const countryItem = checkbox.parentElement;
const countryList = countryItem.parentElement;
const countryTitle = document.getElementById("country-selection-text"); // Get the title element
if (checkbox.checked) {
// Move the selected country item to the top, after the title
countryList.insertBefore(countryItem, countryTitle.nextSibling);
} else {
// Move the unselected country item back to its original position
const originalIndex = parseInt(countryItem.dataset.index);
const previousCountryItem = countryList.querySelector(`[data-index="${originalIndex - 1}"]`);
if (previousCountryItem) {
// Insert the country item after the previousCountryItem
countryList.insertBefore(countryItem, previousCountryItem.nextSibling);
} else {
// If previousCountryItem is not found, it means the item should be inserted after the title
countryList.insertBefore(countryItem, countryTitle.nextSibling);
}
}
}
// Function to populate the country list
function populateCountryList(countries) {
const countryList = document.getElementById("country-selection");
countries.features.forEach((feature, index) => {
const countryName = feature.properties.ADMIN;
// Create a div to contain the checkbox and label elements
const countryItem = document.createElement("div");
countryItem.dataset.index = index; // Assign the data-index attribute
// Create a checkbox input element and a label element for each country
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.id = countryName;
checkbox.name = countryName;
checkbox.value = countryName;
const label = document.createElement("label");
label.htmlFor = countryName;
label.textContent = countryName;
// Add the checkbox and label elements to the countryItem container
countryItem.appendChild(checkbox);
countryItem.appendChild(label);
// Add the countryItem container to the country list container
countryList.appendChild(countryItem);
// Add an event listener to the checkbox
checkbox.addEventListener("change", function () {
// Find the layer with the matching name
const layer = countryLayers[checkbox.value];
if (checkbox.checked) {
layer.setStyle({ fillColor: "yellow" });
} else {
layer.setStyle({ fillColor: "#ccc" });
}
// Move the selected/unselected country item
moveCountryItemToTop(checkbox);
// Save the selected countries to localStorage
saveSelectedCountries();
});
});
}
// Function to save the selected countries to localStorage
function saveSelectedCountries() {
const selectedCountries = Array.from(document.querySelectorAll("#country-selection input:checked")).map((checkbox) => checkbox.value);
localStorage.setItem("selectedCountries", JSON.stringify(selectedCountries));
}
// Function to load the selected countries from localStorage
function loadSelectedCountries() {
const selectedCountries = JSON.parse(localStorage.getItem("selectedCountries")) || [];
selectedCountries.forEach((countryName) => {
const checkbox = document.getElementById(countryName);
if (checkbox) {
checkbox.checked = true;
const layer = countryLayers[countryName];
layer.setStyle({ fillColor: "yellow" });
moveCountryItemToTop(checkbox);
}
});
}
// Fetch GeoJSON data for countries and add it to the map
fetch("https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson")
.then((response) => response.json())
.then((countries) => {
populateCountryList(countries);
L.geoJSON(countries, {
style: function () {
return { color: "#000", weight: 1, fillColor: "#ccc", fillOpacity: 0.7 };
},
onEachFeature: function (feature, layer) {
// Store a reference to each country layer by its name
countryLayers[feature.properties.ADMIN] = layer;
// Bind a tooltip to each country layer with the country name
layer.bindTooltip(feature.properties.ADMIN, { direction: 'auto' });
// Add click event listener to each country layer
layer.on("click", function () {
const checkbox = document.getElementById(feature.properties.ADMIN);
checkbox.checked = !checkbox.checked;
if (checkbox.checked) {
layer.setStyle({ fillColor: "yellow" });
} else {
layer.setStyle({ fillColor: "#ccc" });
}
moveCountryItemToTop(checkbox);
saveSelectedCountries();
});
},
}).addTo(map);
})
.then(() => {
// Load the selected countries from localStorage when the map is ready
map.whenReady(loadSelectedCountries);
});
© Tommy Poulin-Corriveau.