How to Create a Custom Map Adapter for the Map View in DHTMLX Scheduler

The Map view is one of 10 customizable options for presenting upcoming events in our JavaScript scheduling component. By default, it allows displaying the list of planned activities on a geographical map from popular map providers such as Google maps, OpenStreetMap and Mapbox. If these default options don’t meet your needs, the latest Scheduler version features the capability to add a custom map adapter to your calendar.

In this tutorial, you will learn how to add the Maptiler library to the Map view of your JavaScript scheduler built with DHTMLX.

Step-By-Step Guide On Adding a Custom Map Adapter to Scheduler

MapTiler is a powerful map provider that offers customizable and high-quality map tiles used by developers in various projects, including web apps. In the sample below, you can see a JavaScript scheduler with a map view, where you can review all upcoming appointments on the map from MapTiler.

Check the sample >

So without further ado, let us explain in detail how to add a similar functionality to your web project.

Step 1: Initializing Scheduler

You can initialize DHTMLX Scheduler using the init method. As a parameter, the method takes an HTML container (or its ID), where the Scheduler will be placed in.

scheduler.init("scheduler_here");

For more details on the initialization process, check out this documentation page.

Step 2: Adding Map View to Scheduler

Since the Map view is not a default option in Scheduler, you have to add it. You can find all the necessary instructions on this task on this page.

So, if you follow the instructions, the default scheduler with the Map view should look like in this sample.

Step 3: Creating a Custom Map Adapter

Now, we come to integrating a custom map adapter. For this purpose, you need to create a class that implements the Scheduler map adapter interface, which consists of 9 methods described in this section of our documentation.

These methods represent the main actions that users can perform using the Scheduler’s map view.

To create the Maptiler adapter, use the following code:

export class maptilerAdapter {
    constructor(scheduler) {
        this.map = null;
        this.options = {};
        this._markers = [];
        this.scheduler = scheduler;
    }
    initialize(container, options) {
        maptilersdk.config.apiKey = options.accessToken;
        const map = new maptilersdk.Map({
          container: container, // container's id or the HTML element in which the SDK will render the map
          style: maptilersdk.MapStyle.STREETS, // style of the map
          center:[options.initial_position.lng, options.initial_position.lat], // starting position [lng, lat]
          zoom: options.initial_zoom // starting zoom
        });
        }
}

The MapTiler’s map is initialized with the use of maptilersdk.Map API, where you specify the container property with the first parameter of the initialize() method. The other properties can be used as per your discretion.

Pay attention to the following line of code:

maptilersdk.config.apikey = options.accesstoken;

Here, options.accesstoken refers to your MapTiler API key, which should be set in the scheduler.config.map_settings.accesstoken config.

Step 4: Creating Events on Map

You can enable end-users to add locations of appointments on the map with a double-click. You need to apply the dblclick event handler to achieve this goal. In this event handler, you should create a scheduler event using the coordinates from the native event object (e.lnglat.lat, e.lnglat.lng). In addition, you have to specify the event’s location by sending a request to the specified geocode.

Here’s an example of how this can be implemented:

map.on("dblclick",async function(e){
    let response = await fetch(`https://api.maptiler.com/geocoding/${e.lngLat.lng},${e.lngLat.lat}.json?key=${options.accessToken}`).then(response => response.json());
    if (response.features){
        let address = response.features[0].place_name_en;
        scheduler.addEventNow({
            lat: e.lngLat.lat,
            lng: e.lngLat.lng,
            event_location: address,
            start_date: scheduler.getState().date,
            end_date: scheduler.date.add(scheduler.getState().date, scheduler.config.time_step, "minute")
        });
    } else {
        console.error("unable receive a position of the event");
    }
});
Step 5: Displaying Events on Map with Markers

Now, it is time to show you the possibility of displaying upcoming events in your calendar with special markers. Under the hood, you can work with markers using several methods such as addEventMarker(event), removeEventMarker(eventId), updateEventMarker(event), and clearEventMarkers().

In our scenario, it works the following way:

addEventMarker(event) {
    let config = [
        event.lng,
        event.lat
    ]
    if (!event.lat || !event.lng) {
        config = [this.options.error_position.lng, this.options.error_position.lat];
    }
    // create the popup
    const popup = new maptilersdk.Popup({ offset: 25 }).setHTML(this.scheduler.templates.map_info_content(event));
    // create the marker
    const marker = new maptilersdk.Marker()
        .setLngLat(config)
        .setPopup(popup)
        .addTo(this.map);
    // create the tooltip
    const tooltip = new maptilersdk.Popup({
            closeButton: false,
            closeOnClick: false
        });
    const markerInfo = {event, marker};
    // we need to collect all markers in order to find the required one when we will ipdate or delete it
    this._markers.push(markerInfo);
}

removeEventMarker(eventId) {
    for (let i = 0; i < this._markers.length; i++) {
        if (eventId == this._markers[i].event.id) {
            this._markers[i].marker.remove();
            this._markers.splice(i,1);
            i--;
        }
    }
}

updateEventMarker(event) {
    for (let i = 0; i < this._markers.length; i++) {
        if(this._markers[i].event.id == event.id) {
            this._markers[i].event = event;
            if (!event.lat || !event.lng){
                this._markers[i].marker.setLngLat([this.options.error_position.lng, this.options.error_position.lat]);
            } else {
                this._markers[i].marker.setLngLat([event.lng, event.lat]);
            }
        }
    }
}

clearEventMarkers() {
    for (let i = 0; i <this._markers.length; i++) {
        this._markers[i].marker.remove();
    }
    this._markers = [];
}

All of these methods should include a simple storage system for the created markers, allowing for easy updating and deleting of markers from the map. In our case, it is done with the this._markers array, which stores the information about the markers and associated events.

To display a marker on the map when events are clicked on in the grid area, you should utilize the onEventClick(event) function. In code, it is implemented as follows:

onEventClick(event) {
    // move to the marker on the map when the event is clicked in the description area
    if (this._markers && this._markers.length > 0) {
        for (let i = 0; i <  this._markers.length; i++) {
            const popup = this._markers[i].marker.getPopup();
            if (popup.isOpen()){
                popup.remove();
            }
            if (event.id ==  this._markers[i].event.id) {
                this._markers[i].marker.togglePopup();
                if (event.lat && event.lng) {
                    this.setView(event.lat, event.lng, this.options.zoom_after_resolve || this.options.initial_zoom);
                } else {
                    this.setView(this.options.error_position.lat, this.options.error_position.lng, this.options.zoom_after_resolve || this.options.initial_zoom);
                }
            }
        }
    }
}
Step 6: Adjusting Map View Display

The MapTiler’s API allows for precise control over the map’s display. For instance, you can use the setView(latitude, longitude, zoom) method to adjust the view of the map to a certain location determined by the latitude and longitude coordinates, along with the desired zoom level. As a result, you set the required map’s focus, ensuring that end-users can easily view and interact with the relevant geographical information.

setView(latitude, longitude, zoom) {
   this.map.setCenter([longitude, latitude]);
   this.map.setZoom(zoom);
}

The scheduler automatically sets the default location for the event specified in the config, if the database doesn’t store the event’s coordinates. So there should be the resolveAddress(string) method which gets the position(lng, lat) of the event from the event.location property by sending a request to the specified geocode.

This is how it should be implemented:

async resolveAddress(string) {
    // get the position(lng,lat) of the event from event.location if the event doesn't have event.lat or event.lng
    let response = await fetch(`https://api.maptiler.com/geocoding/${string}.json?key=${this.options.accessToken}`).then(response => response.json());
    let position = {};
    if (response && response.features.length) {
        position.lng = response.features[0].center[0];
        position.lat = response.features[0].center[1];
    } else {
        console.error(`Unable recieve a position of the event's location: ${string}`);
    }
    return position;
}

The destroy() method is used to delete the map instance:

destroy(container) {
      this.map.remove();
      while (container.firstChild) {
         container.firstChild.remove();
      }
      container.innerHTML = "";
}

All in all, maptilerAdapter.js should contain 9 methods to function correctly as described above.

Step 7: Adding the New Map Adapter to Scheduler

The final step is to add the new map adapter to your JS scheduling calendar. To do that, you need to import the module and add the adapter to scheduler.ext.mapView.adapters, specifying its name in the scheduler.config.map_view_provider configuration. As a result, the schedulers configuration will look like this:

scheduler.config.header = [
    "day",
    "week",
    "month",
    "map",
    "date",
    "prev",
    "today",
    "next"
];

// activate the Map view extension
scheduler.plugins({
    map_view: true
});

scheduler.ext.mapView.adapters.maptiler = new maptilerAdapter(scheduler); // add new adapter to the extension
scheduler.config.map_view_provider = "maptiler"; // specify the map provider
scheduler.config.map_settings.accessToken = "8alNOUBUrHjEje2Qmkfc"; // specify the MapTiler API key.
scheduler.config.map_settings.initial_zoom = 8;

scheduler.locale.labels.map_tab = "Map";
scheduler.locale.labels.section_location = "Location";
scheduler.xy.map_date_width = 180; // date column width
scheduler.xy.map_description_width = 400; // description column width
scheduler.config.map_start = new Date(2019, 3, 1);
scheduler.config.map_end = new Date(2025, 9, 1);

scheduler.config.lightbox.sections = [
    { name: "description", height: 50, map_to: "text", type: "textarea", focus: true },
    { name: "location", height: 43, map_to: "event_location", type: "textarea" },
    { name: "time", height: 72, type: "time", map_to: "auto" }
];

scheduler.init('scheduler_here', new Date(2024, 5, 1), "map");

const mapEvents = [
    { "id": 278, "start_date": "2024-07-22 12:10:00", "end_date": "2024-07-22 12:15:00", "text": "Sudan", "event_location": "Janub Kurdufan, Sudan", "lat": 7.7172005929348435, "lng": 387.9898595809937 },
    { "id": 285, "start_date": "2024-08-01 02:40:00", "end_date": "2024-08-01 15:05:00", "text": "Ships", "event_location": "Australia", "lat": -28.08958685494885, "lng": 513.4549713134767 },
    { "id": 286, "start_date": "2024-09-15 00:00:00", "end_date": "2024-09-15 00:05:00", "text": "Argentina", "event_location": "Argentina", "lat": -33.288010117378505, "lng": 293.6837553977967 },
    { "id": 90, "start_date": "2024-09-16 00:00:00", "end_date": "2024-09-16 00:05:00", "text": "Berlin", "event_location": "Berlin", "lat": 52.523403, "lng": 13.411400 },
    { "id": 268, "start_date": "2024-07-22 11:35:00", "end_date": "2024-07-22 11:40:00", "text": "Brazil", "event_location": "Brazil", "lat": -15.813189304438533, "lng": -47.91412353515626 }
];

scheduler.parse(mapEvents);

These are the main steps that will allow you to integrate a custom map adapter based on MapTiler to your JavaScript scheduling calendar like in our sample.

Conclusion

The Map view in DHTMLX Scheduler offers a geographical perspective on event scheduling. With the ability to add custom map adapters, the Map view in our JS scheduling component provides greater flexibility in selection of map options that best suits the project’s needs. If you are new to DHTMLX Scheduler but need a comprehensive scheduling tool with multiple views for your project, including the map view, download a free 30-day trial version.

Related Materials

Advance your web development with DHTMLX

Gantt chart
Event calendar
Diagram library
30+ other JS components