Project Overview
In this weekend project, you'll apply the DOM manipulation and Browser API knowledge you've gained throughout Module 10 to build a practical, interactive web application. You'll create a Travel Explorer application that helps users discover and save travel destinations, with features like location detection, interactive maps, offline support, and a responsive user interface.
To guide our development process, we'll use George Polya's renowned 4-step problem solving approach:
the Problem] --> B[2. Devise
a Plan] B --> C[3. Execute
the Plan] C --> D[4. Look Back
& Reflect] D -.-> A
Polya's approach is particularly valuable for web development projects, as it encourages thoughtful planning before coding, systematic execution, and critical reflection that leads to better solutions. Let's explore how each step applies to our weekend project.
George Polya's Problem Solving Approach
George Polya (1887-1985) was a renowned mathematician who developed a systematic approach to problem solving in his book "How to Solve It" (1945). His method has been widely adopted across disciplines, including computer science and software development.
The four steps are:
- Understand the Problem: Clearly define what you're trying to accomplish
- Devise a Plan: Create a strategy before you start coding
- Execute the Plan: Implement your solution systematically
- Look Back: Review your solution, learn from it, and refine it
Step 1: Understand the Problem
The first step in Polya's approach is to clearly define and understand the problem. For our Travel Explorer application, let's break down what we're trying to achieve:
Project Requirements
- Core Functionality: Create an application where users can discover travel destinations, view details, and save favorites
- DOM Manipulation: Dynamic content creation, form handling, event listeners, and UI updates
- Browser API Integration: Implement at least 3 browser APIs including Geolocation and Web Storage
- Responsive Design: Application should work well across different screen sizes
- Offline Capability: Some functionality should work without an internet connection
Key Questions
As Polya suggests, we should ask clarifying questions:
- What data will our application need? Travel destination information including names, descriptions, images, coordinates, and user preferences
- Which browser APIs are most relevant? Geolocation API, Web Storage API, Fetch API, and potentially MediaDevices API
- What are the main user interactions? Searching destinations, saving favorites, viewing details, and getting location-based recommendations
- What are potential limitations or challenges? API rate limits, offline functionality, handling slow connections, browser compatibility
Project Scope
To keep the project manageable for a weekend:
- We'll use a small, pre-defined set of travel destinations (with option to expand)
- We'll focus on core features rather than authentication or server-side persistence
- We'll prioritize user experience and browser API integration over complex design
Applying Polya's First Step
Notice how we're taking time to understand what we're building before writing any code. This is equivalent to carefully reading a math problem and identifying what's given and what's needed before attempting to solve it.
Key activities in this step include:
- Identifying the main objectives
- Clarifying requirements
- Setting boundaries (what's in scope vs. out of scope)
- Anticipating challenges
Step 2: Devise a Plan
Now that we understand the problem, we need to devise a plan for building our Travel Explorer app. This includes architecture decisions, feature prioritization, and implementation strategy.
Application Architecture
Let's outline the structure of our application:
Feature Breakdown
Let's break down our application into manageable features:
- Destination Browsing: Grid/list view of travel destinations with filtering
- Destination Details: Modal or page showing comprehensive destination information
- Geolocation Integration: Obtain user's location and show nearby destinations
- Favorites System: Save and retrieve user's favorite destinations using localStorage
- Offline Support: Cache destinations and allow offline browsing of basic information
- Responsive Layout: Adapt UI for different screen sizes and orientations
- Media Capture (Bonus): Allow users to take photos and add notes to destinations
Implementation Strategy
We'll follow this step-by-step approach:
- Set up the basic HTML structure and CSS styling
- Create the destination data structure and rendering logic
- Implement basic DOM manipulation for the user interface
- Add Geolocation API integration
- Implement localStorage for saving favorites
- Add offline capabilities using cached data
- Enhance with additional browser APIs
- Polish the user interface and experience
Technology Choices
For this project, we'll use:
- HTML5 for structure
- CSS3 for styling, with Flexbox and Grid for layout
- Vanilla JavaScript for functionality (no frameworks)
- Fetch API for data retrieval
- Leaflet.js (open-source library) for maps
Applying Polya's Second Step
In this step, we're strategizing our approach before diving into code. Just as a mathematician would consider different formulas or techniques that might apply to a problem, we're evaluating various technologies and approaches.
Key activities in this step include:
- Breaking down the problem into smaller, manageable parts
- Identifying relationships between components
- Determining a sequence for implementation
- Selecting appropriate tools and techniques
Step 3: Execute the Plan
Now we'll implement our Travel Explorer application step by step. For each component, we'll show the code and explain key implementation details. As Polya suggests, we'll methodically work through our plan, carefully implementing each part of the solution.
Project Structure
Start by setting up the basic file structure:
travel-explorer/
├── index.html # Main HTML file
├── styles/
│ ├── main.css # Main CSS styles
│ └── responsive.css # Responsive design styles
├── scripts/
│ ├── app.js # Main application logic
│ ├── data.js # Destination data
│ ├── ui.js # UI rendering functions
│ ├── storage.js # LocalStorage handling
│ ├── geolocation.js # Geolocation functionality
│ └── map.js # Map integration
└── assets/
└── images/ # Destination images
HTML Structure
Let's create the basic HTML structure for our application:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Travel Explorer</title>
<link rel="stylesheet" href="styles/main.css">
<link rel="stylesheet" href="styles/responsive.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
</head>
<body>
<header>
<div class="logo-container">
<h1>Travel Explorer</h1>
</div>
<nav>
<ul>
<li><a href="#" id="nav-destinations" class="active">Destinations</a></li>
<li><a href="#" id="nav-favorites">Favorites</a></li>
<li><a href="#" id="nav-nearby">Nearby</a></li>
</ul>
</nav>
</header>
<main>
<section id="search-section">
<div class="search-container">
<input type="text" id="search-input" placeholder="Search destinations...">
<select id="filter-continent">
<option value="">All Continents</option>
<option value="asia">Asia</option>
<option value="africa">Africa</option>
<option value="europe">Europe</option>
<option value="north-america">North America</option>
<option value="south-america">South America</option>
<option value="oceania">Oceania</option>
<option value="antarctica">Antarctica</option>
</select>
<button id="search-button">Search</button>
</div>
<div class="view-toggle">
<button id="grid-view-btn" class="active">Grid View</button>
<button id="map-view-btn">Map View</button>
</div>
</section>
<section id="destinations-section" class="active-section">
<div id="destinations-grid" class="destinations-container">
<!-- Destination cards will be inserted here -->
</div>
</section>
<section id="map-section" class="hidden-section">
<div id="map-container"></div>
</section>
<section id="favorites-section" class="hidden-section">
<div id="favorites-container">
<h2>Your Favorite Destinations</h2>
<div id="favorites-grid" class="destinations-container">
<!-- Favorite destination cards will be inserted here -->
</div>
</div>
</section>
<section id="nearby-section" class="hidden-section">
<div id="nearby-container">
<h2>Destinations Near You</h2>
<div id="nearby-message"></div>
<div id="nearby-grid" class="destinations-container">
<!-- Nearby destination cards will be inserted here -->
</div>
</div>
</section>
</main>
<div id="destination-modal" class="modal">
<div class="modal-content">
<span class="close-modal">×</span>
<div id="modal-content-container">
<!-- Destination details will be inserted here -->
</div>
</div>
</div>
<div id="offline-notification" class="hidden">
You are currently offline. Some features may be limited.
</div>
<footer>
<p>© 2025 Travel Explorer</p>
</footer>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="scripts/data.js"></script>
<script src="scripts/storage.js"></script>
<script src="scripts/geolocation.js"></script>
<script src="scripts/map.js"></script>
<script src="scripts/ui.js"></script>
<script src="scripts/app.js"></script>
</body>
</html>
Creating the Destination Data
Let's define our data structure in data.js:
// data.js
const destinations = [
{
id: 'paris',
name: 'Paris',
country: 'France',
continent: 'europe',
description: 'Known as the City of Light, Paris is famous for its iconic Eiffel Tower, world-class cuisine, and remarkable art museums including the Louvre.',
longDescription: 'Paris, the capital of France, is one of the world\'s most beautiful cities, known for its iconic landmarks such as the Eiffel Tower, Notre-Dame Cathedral, and the Louvre Museum. The city is renowned for its rich history, art, fashion, gastronomy, and culture. Stroll along the Seine River, explore charming neighborhoods like Montmartre, and enjoy croissants at sidewalk cafés. Paris offers a perfect blend of history, art, and contemporary urban life.',
image: 'assets/images/paris.jpg',
coordinates: {
lat: 48.8566,
lng: 2.3522
},
tags: ['romantic', 'cultural', 'historic', 'food']
},
{
id: 'kyoto',
name: 'Kyoto',
country: 'Japan',
continent: 'asia',
description: 'Japan\'s cultural capital, Kyoto is home to over 1,600 Buddhist temples, 400 Shinto shrines, and 17 UNESCO World Heritage sites.',
longDescription: 'Kyoto served as Japan\'s capital for over a millennium and remains the heart of traditional Japanese culture. The city is home to an astonishing 17 UNESCO World Heritage Sites, including the golden Kinkaku-ji Temple and the bamboo groves of Arashiyama. Visitors can experience authentic Japanese traditions from tea ceremonies to geisha performances. With its perfectly preserved temples, gardens, imperial palaces, and traditional wooden houses, Kyoto offers a glimpse into Japan\'s rich cultural heritage.',
image: 'assets/images/kyoto.jpg',
coordinates: {
lat: 35.0116,
lng: 135.7681
},
tags: ['cultural', 'historic', 'temples', 'traditional']
}
// Add 8-10 more destinations with similar structure
];
And include necessary functions to work with this data:
// Function to retrieve all destinations
function getAllDestinations() {
return destinations;
}
// Function to get a destination by ID
function getDestinationById(id) {
return destinations.find(destination => destination.id === id);
}
// Function to search destinations by name or country
function searchDestinations(query) {
if (!query) return destinations;
const searchTerm = query.toLowerCase();
return destinations.filter(destination =>
destination.name.toLowerCase().includes(searchTerm) ||
destination.country.toLowerCase().includes(searchTerm) ||
destination.tags.some(tag => tag.toLowerCase().includes(searchTerm))
);
}
// Function to filter destinations by continent
function filterByContinent(continent) {
if (!continent) return destinations;
return destinations.filter(destination =>
destination.continent === continent
);
}
// Function to search and filter combined
function searchAndFilter(query, continent) {
let results = destinations;
if (query) {
const searchTerm = query.toLowerCase();
results = results.filter(destination =>
destination.name.toLowerCase().includes(searchTerm) ||
destination.country.toLowerCase().includes(searchTerm) ||
destination.tags.some(tag => tag.toLowerCase().includes(searchTerm))
);
}
if (continent) {
results = results.filter(destination =>
destination.continent === continent
);
}
return results;
}
// Function to find destinations near coordinates
function findNearbyDestinations(lat, lng, maxDistance = 2000) { // distance in km
return destinations.map(destination => {
const distance = calculateDistance(
lat, lng,
destination.coordinates.lat,
destination.coordinates.lng
);
return {
...destination,
distance: distance
};
})
.filter(destination => destination.distance <= maxDistance)
.sort((a, b) => a.distance - b.distance);
}
// Haversine formula to calculate distance between coordinates
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371; // Earth's radius in km
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
const distance = R * c; // Distance in km
return distance;
}
LocalStorage Management
Let's implement the storage functionality in storage.js:
// storage.js
const StorageManager = {
// Save destinations to favorites
saveFavorite(destination) {
try {
let favorites = this.getFavorites();
// Check if already in favorites
if (!favorites.some(fav => fav.id === destination.id)) {
favorites.push(destination);
localStorage.setItem('travelExplorerFavorites', JSON.stringify(favorites));
return true;
}
return false;
} catch (error) {
console.error('Error saving favorite:', error);
return false;
}
},
// Remove destination from favorites
removeFavorite(destinationId) {
try {
let favorites = this.getFavorites();
const updatedFavorites = favorites.filter(fav => fav.id !== destinationId);
localStorage.setItem('travelExplorerFavorites', JSON.stringify(updatedFavorites));
return true;
} catch (error) {
console.error('Error removing favorite:', error);
return false;
}
},
// Get all favorites
getFavorites() {
try {
const favorites = localStorage.getItem('travelExplorerFavorites');
return favorites ? JSON.parse(favorites) : [];
} catch (error) {
console.error('Error retrieving favorites:', error);
return [];
}
},
// Check if a destination is in favorites
isFavorite(destinationId) {
const favorites = this.getFavorites();
return favorites.some(fav => fav.id === destinationId);
},
// Save user preferences
savePreference(key, value) {
try {
let preferences = this.getPreferences();
preferences[key] = value;
localStorage.setItem('travelExplorerPreferences', JSON.stringify(preferences));
return true;
} catch (error) {
console.error('Error saving preference:', error);
return false;
}
},
// More storage methods...
}
Geolocation Integration
Let's implement the geolocation functionality in geolocation.js:
// geolocation.js
const GeolocationManager = {
// Get current position
getCurrentPosition() {
return new Promise((resolve, reject) => {
// Check if geolocation is supported
if (!navigator.geolocation) {
reject(new Error('Geolocation is not supported by your browser'));
return;
}
// Try getting the current position
navigator.geolocation.getCurrentPosition(
position => {
const coords = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy
};
// Save location to storage for future use
StorageManager.saveLocation(coords.latitude, coords.longitude);
resolve(coords);
},
error => {
console.error('Geolocation error:', error.message);
// If permission denied or other error, try getting saved location
const savedLocation = StorageManager.getLocation();
if (savedLocation) {
resolve(savedLocation);
} else {
reject(error);
}
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 300000 // 5 minutes
}
);
});
},
// Find nearby destinations
findNearbyDestinations() {
return new Promise((resolve, reject) => {
this.getCurrentPosition()
.then(position => {
const nearbyDestinations = findNearbyDestinations(
position.latitude,
position.longitude,
2000 // 2000 km radius
);
resolve({
position: position,
destinations: nearbyDestinations
});
})
.catch(error => {
reject(error);
});
});
}
}
UI Management (Snippet)
Let's implement part of the UI rendering in ui.js:
// ui.js
const UI = {
// Initialize UI elements and event listeners
init() {
// Set up navigation tabs
document.getElementById('nav-destinations').addEventListener('click', (e) => {
e.preventDefault();
this.showSection('destinations-section');
this.updateActiveNav('nav-destinations');
});
document.getElementById('nav-favorites').addEventListener('click', (e) => {
e.preventDefault();
this.showSection('favorites-section');
this.updateActiveNav('nav-favorites');
this.renderFavorites();
});
// Set up view toggle
document.getElementById('grid-view-btn').addEventListener('click', () => {
this.showSection('destinations-section');
this.updateViewToggle('grid-view-btn');
});
// More initialization code...
},
// Create a destination card element
createDestinationCard(destination) {
const card = document.createElement('div');
card.className = 'destination-card';
card.setAttribute('data-id', destination.id);
const isFavorite = StorageManager.isFavorite(destination.id);
card.innerHTML = `
<div class="card-image" style="background-image: url('${destination.image || 'assets/images/placeholder.jpg'}')">
<div class="card-favorite ${isFavorite ? 'is-favorite' : ''}">
<i class="favorite-icon"></i>
</div>
</div>
<div class="card-content">
<h3>${destination.name}, ${destination.country}</h3>
<p>${destination.description}</p>
<div class="card-tags">
${destination.tags ? destination.tags.slice(0, 3).map(tag => `<span class="tag">${tag}</span>`).join('') : ''}
</div>
${destination.distance ? `<div class="distance-badge">${Math.round(destination.distance)} km away</div>` : ''}
<button class="view-details-btn">View Details</button>
</div>
`;
// Add event listeners for card buttons
// ...
return card;
}
// More UI methods...
}
CSS Styling (Snippet)
Here's a snippet of the main CSS styling:
/* main.css */
/* Base styles and reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f4f7f9;
}
a {
text-decoration: none;
color: #2c3e50;
}
/* Header styles */
header {
background-color: #ffffff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
position: sticky;
top: 0;
z-index: 100;
}
/* Destination card styles */
.destinations-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
}
.destination-card {
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
transition: transform 0.3s, box-shadow 0.3s;
}
.favorite-icon {
width: 1.2rem;
height: 1.2rem;
background-image: url('data:image/svg+xml;utf8,');
background-repeat: no-repeat;
background-position: center;
}
Step 4: Look Back & Reflect
The final step in Polya's problem-solving approach is to look back at the solution, evaluate it, and learn from the experience. Let's reflect on our Travel Explorer application.
Application Review
Let's evaluate our application against our original requirements:
| Requirement | Implementation | Success Factors |
|---|---|---|
| Core Functionality | Destinations can be browsed, filtered, viewed in detail, and saved as favorites | Organized codebase with separation of concerns |
| DOM Manipulation | Dynamic content generation, interactive UI elements, event handling | Modular approach with UI functions in separate file |
| Browser API Integration | Geolocation, Web Storage, and Fetch APIs implemented | Proper error handling and fallbacks for each API |
| Responsive Design | Application works on mobile, tablet, and desktop | Mobile-first approach with media queries |
| Offline Capability | Destinations are cached, and favorites are persistent | Strategic use of localStorage with quota management |
Lessons Learned
Through building this application, we've gained valuable insights:
- Modular Design: Separating functionality into logical modules made the code more maintainable and easier to debug
- Progressive Enhancement: Building core functionality first and then enhancing with APIs ensures the app works even if some features aren't available
- Error Handling: Robust error handling is crucial, especially for browser APIs that may fail due to permissions or connectivity issues
- User Experience: Creating a seamless online/offline experience requires careful planning and feedback mechanisms
- Performance Considerations: Efficient DOM operations and storage management are essential for smooth performance
Areas for Improvement
If we were to extend this application, we could consider:
- Service Worker: Implementing a service worker for true offline functionality
- Improved Data Management: Using IndexedDB for larger data storage needs
- Performance Optimization: Adding lazy loading for images and virtualization for long lists
- Accessibility Enhancements: More comprehensive ARIA attributes and keyboard navigation
- Additional Browser APIs: Integrating Web Share API, Notification API, or Camera API
Conclusion
This weekend project brings together everything you've learned in Module 10 on DOM manipulation and Browser APIs. By building a practical, interactive Travel Explorer application, you'll solidify your understanding of these concepts and create something you can showcase in your portfolio.
Remember to approach the project using George Polya's problem-solving methodology:
- Understand the Problem: Start by thoroughly understanding the requirements and constraints.
- Devise a Plan: Create a structured plan with modular components before coding.
- Execute the Plan: Systematically implement your solution step by step.
- Look Back: Evaluate your solution, learn from the process, and consider improvements.
This systematic approach will not only help you complete this project successfully but also build valuable problem-solving skills that will serve you throughout your development career.
Good luck, and enjoy building your Travel Explorer application!