Skip to content

Vanilla JavaScript Integration Guide

Learn how to integrate route optimization and mapping into vanilla JavaScript applications using @route-optimization/vanilla.

Installation

bash
npm install @route-optimization/vanilla @route-optimization/core
# or
pnpm add @route-optimization/vanilla @route-optimization/core
# or
yarn add @route-optimization/vanilla @route-optimization/core

API Key Setup

Before using any classes, you'll need a Google Maps API key. There are several ways to configure it:

bash
# .env
GOOGLE_MAPS_API_KEY=your_api_key_here
javascript
// With build tools like Vite
const routeMap = new RouteMap({
  apiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY,
  container: 'map',
});

// With Node.js
const routeMap = new RouteMap({
  apiKey: process.env.GOOGLE_MAPS_API_KEY,
  container: 'map',
});

Option 2: Configuration File

javascript
// config.js
export const config = {
  googleMapsApiKey: 'your_api_key_here',
};

// main.js
import { config } from './config.js';

const routeMap = new RouteMap({
  apiKey: config.googleMapsApiKey,
  container: 'map',
});

Security

Never commit your API key to version control. Use environment variables or secure configuration management. See Authentication Guide for best practices.

Core Concepts

The Vanilla package provides classes with no framework dependencies:

  • RouteMap - Main class for map lifecycle and route management
  • MapRenderer - Utility class for rendering and customization
  • RouteOptimizer - Class for route calculation and optimization

Basic Map Rendering

Simple Route Map

javascript
import { RouteMap } from '@route-optimization/vanilla';

// Get map container
const mapElement = document.getElementById('map');

// Create route map instance
const routeMap = new RouteMap({
  apiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
  center: { lat: 13.7563, lng: 100.5018 }, // Bangkok
  zoom: 12,
});

// Initialize map
routeMap
  .initialize(mapElement)
  .then(() => {
    console.log('Map ready!');

    // Render a route
    routeMap.renderRoute({
      stops: [
        {
          id: '1',
          location: { lat: 13.7563, lng: 100.5018 },
          name: 'Start Location',
        },
        {
          id: '2',
          location: { lat: 13.7467, lng: 100.5352 },
          name: 'End Location',
        },
      ],
    });
  })
  .catch((error) => {
    console.error('Map initialization failed:', error);
  });

With Event Listeners

javascript
const routeMap = new RouteMap({
  apiKey: 'YOUR_API_KEY',
  onError: (error) => {
    console.error('Map error:', error);
    showErrorMessage(error.message);
  },
});

// Button event listeners
document.getElementById('show-route').addEventListener('click', () => {
  if (routeMap.isReady()) {
    routeMap.renderRoute(currentRoute);
  }
});

document.getElementById('clear-route').addEventListener('click', () => {
  routeMap.clearRoute();
});

// Clean up when leaving page
window.addEventListener('beforeunload', () => {
  routeMap.destroy();
});

Map Controls

Center and Zoom

javascript
const routeMap = new RouteMap({ apiKey: 'YOUR_API_KEY' });

await routeMap.initialize(mapElement);

// Set center
routeMap.setCenter({ lat: 13.7563, lng: 100.5018 });

// Set zoom
routeMap.setZoom(15);

// Get current center
const center = routeMap.getCenter();
console.log('Current center:', center);

// Get current zoom
const zoom = routeMap.getZoom();
console.log('Current zoom:', zoom);

Bounds and Fit

javascript
// Fit map to show all stops
const bounds = {
  north: 13.8,
  south: 13.7,
  east: 100.6,
  west: 100.4,
};

routeMap.fitBounds(bounds);

// Get current bounds
const currentBounds = routeMap.getBounds();
console.log('Map bounds:', currentBounds);

Route Optimization

Basic Optimization

javascript
import { RouteOptimizer } from '@route-optimization/vanilla';

// Create optimizer instance
const optimizer = new RouteOptimizer({
  useMockMode: true, // Use mock mode for development
});

// Set up event listeners
optimizer.on('status-change', (status) => {
  console.log('Status:', status);
  updateStatusUI(status);
});

optimizer.on('success', (response) => {
  console.log('Optimization complete:', response);
  displayResults(response);
});

optimizer.on('error', (error) => {
  console.error('Optimization failed:', error);
  showError(error.message);
});

// Initialize
await optimizer.initialize();

// Optimize routes
const request = {
  shipments: [
    {
      id: 'shipment-1',
      deliveryLocation: { latitude: 13.7563, longitude: 100.5018 },
    },
    {
      id: 'shipment-2',
      deliveryLocation: { latitude: 13.7467, longitude: 100.5352 },
    },
  ],
  vehicles: [
    {
      id: 'vehicle-1',
      startLocation: { latitude: 13.7563, longitude: 100.5018 },
    },
  ],
};

const response = await optimizer.optimize(request);

if (response.success) {
  console.log('Routes:', response.routes);
}

Production Mode with Google Cloud

javascript
const optimizer = new RouteOptimizer({
  useMockMode: false,
  projectId: 'your-google-cloud-project',
  credentials: {
    client_email: 'service-account@project.iam.gserviceaccount.com',
    private_key: process.env.GOOGLE_PRIVATE_KEY,
  },
});

Progress Tracking

javascript
// Progress bar element
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');

optimizer.on('progress', ({ progress, step }) => {
  progressBar.style.width = `${progress}%`;
  progressText.textContent = step;
});

optimizer.on('start', () => {
  progressBar.style.width = '0%';
  progressText.textContent = 'Starting...';
});

// Manual progress updates
async function optimizeWithProgress() {
  optimizer.updateProgress(0, 'Initializing...');

  // Simulate progress
  setTimeout(() => {
    optimizer.updateProgress(25, 'Calculating distances...');
  }, 500);

  const response = await optimizer.optimize(request);

  optimizer.updateProgress(75, 'Optimizing routes...');

  if (response.success) {
    optimizer.updateProgress(100, 'Complete!');
  }
}

Complete Example

Combining map rendering with route optimization:

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Route Optimization</title>
    <style>
      body {
        margin: 0;
        font-family: Arial, sans-serif;
        display: flex;
        height: 100vh;
      }

      .sidebar {
        width: 300px;
        padding: 20px;
        background: #f5f5f5;
        overflow-y: auto;
      }

      .map-container {
        flex: 1;
      }

      #map {
        width: 100%;
        height: 100%;
      }

      .location-item {
        padding: 10px;
        margin: 5px 0;
        background: white;
        border-radius: 4px;
      }

      button {
        width: 100%;
        padding: 12px;
        margin: 10px 0;
        background: #4caf50;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
      }

      button:disabled {
        opacity: 0.5;
        cursor: not-allowed;
      }

      .progress {
        width: 100%;
        height: 24px;
        background: #f0f0f0;
        border-radius: 4px;
        overflow: hidden;
        margin: 10px 0;
      }

      .progress-bar {
        height: 100%;
        background: #4caf50;
        transition: width 0.3s ease;
        width: 0%;
      }

      .success {
        padding: 10px;
        background: #e8f5e9;
        color: #2e7d32;
        border-radius: 4px;
        margin: 10px 0;
        display: none;
      }
    </style>
  </head>
  <body>
    <div class="sidebar">
      <h2>Route Optimization</h2>

      <div id="locations"></div>

      <button id="optimize-btn">Optimize Route</button>

      <div class="progress">
        <div id="progress-bar" class="progress-bar"></div>
      </div>
      <p id="progress-text"></p>

      <div id="success-msg" class="success">✓ Route optimized successfully!</div>

      <div id="results"></div>
    </div>

    <div class="map-container">
      <div id="map"></div>
    </div>

    <script type="module">
      import { RouteMap, RouteOptimizer } from '@route-optimization/vanilla';

      // Sample locations
      const locations = [
        { lat: 13.7563, lng: 100.5018, name: 'Bangkok' },
        { lat: 13.7467, lng: 100.5352, name: 'Chatuchak' },
        { lat: 13.7307, lng: 100.5418, name: 'Victory Monument' },
      ];

      // Render locations list
      const locationsEl = document.getElementById('locations');
      locations.forEach((loc) => {
        const div = document.createElement('div');
        div.className = 'location-item';
        div.textContent = loc.name;
        locationsEl.appendChild(div);
      });

      // Initialize map
      const mapElement = document.getElementById('map');
      const routeMap = new RouteMap({
        apiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
        center: { lat: 13.7563, lng: 100.5018 },
        zoom: 12,
        onError: (error) => {
          console.error('Map error:', error);
          alert('Map error: ' + error.message);
        },
      });

      await routeMap.initialize(mapElement);

      // Initialize optimizer
      const optimizer = new RouteOptimizer({
        useMockMode: true,
      });

      // Progress tracking
      const progressBar = document.getElementById('progress-bar');
      const progressText = document.getElementById('progress-text');
      const successMsg = document.getElementById('success-msg');
      const optimizeBtn = document.getElementById('optimize-btn');
      const resultsEl = document.getElementById('results');

      optimizer.on('status-change', (status) => {
        if (status === 'optimizing') {
          optimizeBtn.disabled = true;
          successMsg.style.display = 'none';
        } else if (status === 'success') {
          optimizeBtn.disabled = false;
          successMsg.style.display = 'block';
        } else {
          optimizeBtn.disabled = false;
        }
      });

      optimizer.on('progress', ({ progress, step }) => {
        progressBar.style.width = `${progress}%`;
        progressText.textContent = step;
      });

      optimizer.on('success', (response) => {
        // Display results
        resultsEl.innerHTML = `
        <h3>Results</h3>
        <p>Routes: ${response.routes?.length ?? 0}</p>
        <p>Total Distance: ${response.metrics?.totalDistance ?? 0} km</p>
        <p>Total Duration: ${response.metrics?.totalDuration ?? 0} min</p>
      `;

        // Render on map
        if (response.routes?.[0]) {
          const route = {
            stops:
              response.routes[0].stops?.map((stop, i) => ({
                id: stop.shipmentId || `stop-${i}`,
                location: {
                  lat: stop.location?.latitude ?? 0,
                  lng: stop.location?.longitude ?? 0,
                },
                name: locations[i]?.name ?? `Stop ${i + 1}`,
              })) ?? [],
          };

          routeMap.renderRoute(route);
        }
      });

      optimizer.on('error', (error) => {
        alert('Optimization failed: ' + error.message);
        progressBar.style.width = '0%';
        progressText.textContent = '';
      });

      // Initialize optimizer
      await optimizer.initialize();

      // Optimize button handler
      optimizeBtn.addEventListener('click', async () => {
        const request = {
          shipments: locations.map((loc, i) => ({
            id: `shipment-${i}`,
            deliveryLocation: { latitude: loc.lat, longitude: loc.lng },
          })),
          vehicles: [
            {
              id: 'vehicle-1',
              startLocation: {
                latitude: locations[0].lat,
                longitude: locations[0].lng,
              },
            },
          ],
        };

        optimizer.updateProgress(0, 'Starting...');

        try {
          await optimizer.optimize(request);
        } catch (error) {
          console.error('Optimization error:', error);
        }
      });

      // Cleanup on page unload
      window.addEventListener('beforeunload', () => {
        routeMap.destroy();
        optimizer.destroy();
      });
    </script>
  </body>
</html>

TypeScript Support

The package is written in TypeScript and includes full type definitions:

typescript
import type {
  Route,
  Stop,
  MapConfig,
  OptimizationRequest,
  OptimizationResponse,
} from '@route-optimization/core';

import type {
  RouteMapConfig,
  MapRendererConfig,
  OptimizationStatus,
  RouteOptimizerEvents,
} from '@route-optimization/vanilla';

// Typed map instance
const routeMap: RouteMap = new RouteMap(config);

// Typed optimizer
const optimizer: RouteOptimizer = new RouteOptimizer(config);

Event-Driven Architecture

The Vanilla package uses events for communication:

RouteOptimizer Events

javascript
// Status changes
optimizer.on('status-change', (status) => {
  // 'idle' | 'initializing' | 'optimizing' | 'success' | 'error'
});

// Progress updates
optimizer.on('progress', ({ progress, step }) => {
  // progress: 0-100
  // step: string description
});

// Optimization started
optimizer.on('start', () => {
  console.log('Optimization started');
});

// Optimization succeeded
optimizer.on('success', (response) => {
  console.log('Success:', response);
});

// Optimization failed
optimizer.on('error', (error) => {
  console.error('Error:', error);
});

// State reset
optimizer.on('reset', () => {
  console.log('State reset');
});

// Remove listener
const handler = (status) => console.log(status);
optimizer.on('status-change', handler);
optimizer.off('status-change', handler);

Best Practices

1. Error Handling

Always handle errors properly:

javascript
routeMap
  .initialize(mapElement)
  .then(() => {
    // Map ready
  })
  .catch((error) => {
    console.error('Failed to initialize:', error);
    showErrorMessage(error.message);
  });

2. Cleanup

Clean up resources when done:

javascript
// When changing pages or routes
routeMap.destroy();
optimizer.destroy();

// In Single Page Apps
window.addEventListener('popstate', () => {
  routeMap.destroy();
});

3. State Management

Check state before operations:

javascript
if (routeMap.isReady()) {
  routeMap.renderRoute(route);
}

if (optimizer.isSuccess) {
  const data = optimizer.data;
  // Use optimization data
}

4. Memory Management

Remove event listeners when not needed:

javascript
function setupListeners() {
  const handler = (response) => {
    // Handle response
  };

  optimizer.on('success', handler);

  // Later, remove listener
  return () => {
    optimizer.off('success', handler);
  };
}

const cleanup = setupListeners();
// When done
cleanup();

Module Bundlers

Webpack

javascript
import { RouteMap, RouteOptimizer } from '@route-optimization/vanilla';

Vite

javascript
import { RouteMap } from '@route-optimization/vanilla';

CDN (Browser)

html
<script type="module">
  import { RouteMap } from 'https://cdn.skypack.dev/@route-optimization/vanilla';
</script>

Next Steps

Released under the MIT License.