Vanilla JavaScript Integration Guide
Learn how to integrate route optimization and mapping into vanilla JavaScript applications using @route-optimization/vanilla.
Installation
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/coreAPI Key Setup
Before using any classes, you'll need a Google Maps API key. There are several ways to configure it:
Option 1: Environment Variables (Recommended)
# .env
GOOGLE_MAPS_API_KEY=your_api_key_here// 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
// 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 managementMapRenderer- Utility class for rendering and customizationRouteOptimizer- Class for route calculation and optimization
Basic Map Rendering
Simple Route Map
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
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
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
// 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
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
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
// 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:
<!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:
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
// 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:
routeMap
.initialize(mapElement)
.then(() => {
// Map ready
})
.catch((error) => {
console.error('Failed to initialize:', error);
showErrorMessage(error.message);
});2. Cleanup
Clean up resources when done:
// 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:
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:
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
import { RouteMap, RouteOptimizer } from '@route-optimization/vanilla';Vite
import { RouteMap } from '@route-optimization/vanilla';CDN (Browser)
<script type="module">
import { RouteMap } from 'https://cdn.skypack.dev/@route-optimization/vanilla';
</script>Next Steps
- Learn about React Integration
- Explore Route Calculation in depth
- Check out API Reference