Skip to content

Latest commit

 

History

History
440 lines (356 loc) · 8.4 KB

File metadata and controls

440 lines (356 loc) · 8.4 KB

TypeScript Guide

ol-contextmenu includes full TypeScript support with comprehensive type definitions.

Installation

npm install ol ol-contextmenu

Type definitions are included automatically—no need for @types packages.

📖 For detailed installation instructions, see Getting Started.

Basic Usage

Importing

import ContextMenu, {
    type Item,
    type Options,
    type SingleItem,
    type CallbackObject,
    type ContextMenuEvent,
} from 'ol-contextmenu';
import type Map from 'ol/Map';
import type { Coordinate } from 'ol/coordinate';

Note: Use type imports for better tree-shaking and to avoid runtime imports.

Creating a Context Menu

import ContextMenu, { type Item } from 'ol-contextmenu';
import type Map from 'ol/Map';

const items: Item[] = [
    {
        text: 'Center map here',
        callback: (obj, map: Map) => {
            map.getView().animate({
                center: obj.coordinate,
                duration: 700,
            });
        },
    },
    '-', // Separator (type-safe)
    {
        text: 'Zoom In',
        callback: (obj, map: Map) => {
            const view = map.getView();
            view.animate({
                zoom: view.getZoom()! + 1,
                duration: 500,
            });
        },
    },
];

const contextmenu = new ContextMenu({
    width: 170,
    defaultItems: true,
    items,
});

Available Types

Item

Union type representing any menu item:

type Item = SingleItem | ItemWithNested | ItemSeparator;

SingleItem

A standard menu item with a callback:

interface SingleItem {
    text: string;
    callback: (obj: CallbackObject, map: Map) => void;
    icon?: string;
    classname?: string;
    data?: any;
}

ItemWithNested

A menu item containing nested subitems:

interface ItemWithNested {
    text: string;
    items: Item[];
    icon?: string;
    classname?: string;
}

ItemSeparator

Menu separator (literal type):

type ItemSeparator = '-';

Options

Constructor options:

interface Options {
    width?: number;
    defaultItems?: boolean;
    items?: Item[];
    eventType?: EventTypes;
}

CallbackObject

Data passed to callbacks:

interface CallbackObject {
    coordinate: Coordinate;
    data?: any;
}

ContextMenuEvent

Event data for beforeopen and open events:

class ContextMenuEvent extends BaseEvent {
    map: Map;
    coordinate: Coordinate;
    pixel: Pixel;
    originalEvent: MouseEvent;
}

EventTypes

Enum for event trigger types:

enum EventTypes {
    CONTEXTMENU = 'contextmenu',
    CLICK = 'click',
    DBLCLICK = 'dblclick',
}

CustomEventTypes

Enum for menu events:

enum CustomEventTypes {
    BEFOREOPEN = 'beforeopen',
    OPEN = 'open',
    CLOSE = 'close',
}

Type-Safe Patterns

Typed Callback Functions

import type { CallbackObject } from 'ol-contextmenu';
import type Map from 'ol/Map';

const handleCenter = (obj: CallbackObject, map: Map): void => {
    map.getView().setCenter(obj.coordinate);
};

const handleZoom = (obj: CallbackObject, map: Map): void => {
    const view = map.getView();
    const currentZoom = view.getZoom();

    if (currentZoom !== undefined) {
        view.setZoom(currentZoom + 1);
    }
};

const items: Item[] = [
    { text: 'Center', callback: handleCenter },
    { text: 'Zoom In', callback: handleZoom },
];

Custom Data with Type Safety

interface MarkerData {
    id: number;
    name: string;
    type: 'marker' | 'poi';
}

const markerItem: SingleItem = {
    text: 'Delete Marker',
    data: {
        id: 1,
        name: 'My Marker',
        type: 'marker',
    } as MarkerData,
    callback: (obj) => {
        const data = obj.data as MarkerData;
        console.log(`Deleting ${data.type}: ${data.name}`);
    },
};

Event Handlers

import { CustomEventTypes, type ContextMenuEvent } from 'ol-contextmenu';

contextmenu.on(CustomEventTypes.BEFOREOPEN, (evt: ContextMenuEvent) => {
    console.log('Clicked at:', evt.coordinate);
    console.log('Pixel:', evt.pixel);
    console.log('Map:', evt.map);
});

contextmenu.on(CustomEventTypes.OPEN, (evt: ContextMenuEvent) => {
    const feature = evt.map.forEachFeatureAtPixel(
        evt.pixel,
        (ft) => ft,
    );

    if (feature) {
        console.log('Clicked on feature:', feature.getId());
    }
});

Nested Submenus

const nestedItems: Item[] = [
    {
        text: 'Tools',
        items: [
            {
                text: 'Measure',
                items: [
                    {
                        text: 'Distance',
                        callback: measureDistance,
                    },
                    {
                        text: 'Area',
                        callback: measureArea,
                    },
                ],
            },
            '-',
            {
                text: 'Export',
                callback: exportMap,
            },
        ],
    },
];

Complete TypeScript Example

import Map from 'ol/Map';
import View from 'ol/View';
import TileLayer from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import ContextMenu, {
    type Item,
    type CallbackObject,
    CustomEventTypes,
} from 'ol-contextmenu';

// Initialize map
const map = new Map({
    target: 'map',
    layers: [
        new TileLayer({
            source: new OSM(),
        }),
    ],
    view: new View({
        center: [0, 0],
        zoom: 2,
    }),
});

// Vector layer for markers
const vectorSource = new VectorSource();
const vectorLayer = new VectorLayer({
    source: vectorSource,
});
map.addLayer(vectorLayer);

// Type-safe callback functions
const addMarker = (obj: CallbackObject, map: Map): void => {
    const marker = new Feature({
        geometry: new Point(obj.coordinate),
    });
    vectorSource.addFeature(marker);
    console.log('Marker added at', obj.coordinate);
};

const centerMap = (obj: CallbackObject, map: Map): void => {
    map.getView().animate({
        center: obj.coordinate,
        duration: 700,
    });
};

// Define menu items
const items: Item[] = [
    {
        text: 'Add Marker',
        icon: './marker.png',
        callback: addMarker,
    },
    '-',
    {
        text: 'Center Here',
        callback: centerMap,
    },
];

// Create context menu
const contextmenu = new ContextMenu({
    width: 180,
    defaultItems: true,
    items,
});

// Add to map
map.addControl(contextmenu);

// Event handlers
contextmenu.on(CustomEventTypes.BEFOREOPEN, (evt) => {
    const feature = map.forEachFeatureAtPixel(evt.pixel, (ft) => ft);

    if (feature) {
        contextmenu.clear();
        contextmenu.push({
            text: 'Delete Marker',
            callback: () => {
                vectorSource.removeFeature(feature);
            },
        });
    } else {
        contextmenu.clear();
        contextmenu.extend(items);
        contextmenu.extend(contextmenu.getDefaultItems());
    }
});

Strict Type Checking

Enable strict mode in tsconfig.json for maximum type safety:

{
    "compilerOptions": {
        "strict": true,
        "strictNullChecks": true,
        "strictFunctionTypes": true,
        "noImplicitAny": true,
        "noImplicitThis": true
    }
}

Common Type Issues

Issue: Cannot find module 'ol-contextmenu'

Solution: Make sure the package is installed:

npm install ol-contextmenu

Issue: Type errors with callback

Problem:

// ❌ Error: Type '() => void' is not assignable...
const item = {
    text: 'Test',
    callback: () => console.log('test'),
};

Solution:

// ✅ Correct: Include parameters
const item: SingleItem = {
    text: 'Test',
    callback: (obj, map) => console.log('test'),
};

Issue: Cannot import types

Problem:

// ❌ Wrong
import { ContextMenu, Item } from 'ol-contextmenu';

Solution:

// ✅ Correct
import ContextMenu, { type Item } from 'ol-contextmenu';

See Also