Weekend Project: Interactive Web Application

Building a Travel Explorer App with DOM Manipulation and Browser APIs

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:

flowchart LR A[1. Understand
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:

  1. Understand the Problem: Clearly define what you're trying to accomplish
  2. Devise a Plan: Create a strategy before you start coding
  3. Execute the Plan: Implement your solution systematically
  4. 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

Key Questions

As Polya suggests, we should ask clarifying questions:

Project Scope

To keep the project manageable for a weekend:

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:

flowchart TD A[Travel Explorer App] --> B[UI Components] A --> C[Data Management] A --> D[API Integration] B --> B1[Destination Card] B --> B2[Search/Filter] B --> B3[Map View] B --> B4[Details Modal] B --> B5[Favorites List] C --> C1[Local Storage] C --> C2[Destination Data] C --> C3[User Preferences] D --> D1[Geolocation] D --> D2[Map API] D --> D3[MediaDevices] D --> D4[Web Storage]

Feature Breakdown

Let's break down our application into manageable features:

  1. Destination Browsing: Grid/list view of travel destinations with filtering
  2. Destination Details: Modal or page showing comprehensive destination information
  3. Geolocation Integration: Obtain user's location and show nearby destinations
  4. Favorites System: Save and retrieve user's favorite destinations using localStorage
  5. Offline Support: Cache destinations and allow offline browsing of basic information
  6. Responsive Layout: Adapt UI for different screen sizes and orientations
  7. Media Capture (Bonus): Allow users to take photos and add notes to destinations

Implementation Strategy

We'll follow this step-by-step approach:

  1. Set up the basic HTML structure and CSS styling
  2. Create the destination data structure and rendering logic
  3. Implement basic DOM manipulation for the user interface
  4. Add Geolocation API integration
  5. Implement localStorage for saving favorites
  6. Add offline capabilities using cached data
  7. Enhance with additional browser APIs
  8. Polish the user interface and experience

Technology Choices

For this project, we'll use:

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:

Areas for Improvement

If we were to extend this application, we could consider:

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:

  1. Understand the Problem: Start by thoroughly understanding the requirements and constraints.
  2. Devise a Plan: Create a structured plan with modular components before coding.
  3. Execute the Plan: Systematically implement your solution step by step.
  4. 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!