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

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.