How to Create Leaflet Control and Layer Plugins

In this article, I’ll explain how to implement control and layer plugins for the Leaflet JavaScript mapping library. I’ll focus on explaining the structure and lifecycle of Leaflet plugins. Template code for creating Leaflet control and layer plugins is available on GitHub under the MIT license.

Leaflet Control Plugins

Leaflet control plugins add user interface controls that act on a Leaflet map or it’s layers. Two examples of Leaflet controls are the zoom control and the layers control shown at the top left and top right of the following Leaflet map:

LeafletJS map with zoom and layer controls

Creating a Leaflet Control Plugin

Leaflet control plugins are JavaScript classes that extend the Leaflet L.Control class. A common naming convention is to add the name of the control plugin to Leaflet’s namespace, which is L. The following code creates a control plugin with a class named L.MyLeafletControl:

L.MyLeafletControl = L.Control.extend({
  options: {
    position: 'topright'
  },
  ...
}

The extend method of L.Control takes a single object parameter that contains the properties and methods the plugin will add to the L.Control subclass. Many control plugins have a set of default settings. In the code snippet above, the options object contains a single position setting with a default value of topright.

The standard Leaflet plugin creation pattern is to implement a factory function that enables the creation of the plugin to be chained with other function calls:

L.myLeafletControl({ position: 'bottomright' }).addTo(map);

The common convention is to name the factory function after the class of the control plugin but make the first letter lower case.

L.myLeafletControl = function(options) {
  return new L.MyLeafletControl(options);
};

The Control Plugin Lifecycle

Leaflet calls the following methods of a control plugin when the control is added to a Leaflet map:

  1. initialize()
  2. onAdd()

Leaflet calls the initialize method when an instance of a control plugin is created by calling new directly or by using the factory function:

  1. L.myLeafletControl()
  2. new L.MyLeafletControl()

In the following initialize method, we call L.Util.setOptions to combine the values of the default settings (specified by the options object parameter passed to the L.Class.extend method) with the values of the settings for this instance of the control plugin, which are specified by the options object passed as a parameter to the initialize method.

initialize: function(options) {
  L.Util.setOptions(this, options);
  // Continue initializing the control plugin here.
 }

Any further set up code for the control plugin should be added to the initialize method after the call to setOptions.

Leaflet calls the onAdd method when the control is added to the map with the following method calls:

  • control.addTo(map);
  • map.addControl(control);

Control plugins are user interface elements that Leaflet displays on top of the map. The onAdd method must return the DOM element that contains the user interface of the control. In the following onAdd method, we create a HTML div element with a class of my-leaflet-control to enable the control to be styled with CSS (line 2).

1
2
3
4
5
6
7
8
9
onAdd: function(map) {
  var controlElementTag = 'div';
  var controlElementClass = 'my-leaflet-control';
  var controlElement = L.DomUtil.create(controlElementTag, controlElementClass);

  // Continue implementing the control here.

  return controlElement;
}

After creating the div container for the control, add any other code required to implement the control before returning the control’s DOM element (line 8).

Leaflet calls a third control plugin method called onRemove when the control is removed from the map:

  • control.removeFrom(map);
  • map.removeControl(control);

The onRemove method is the place to tear down your control by releasing resources and removing event listeners, etc.

onRemove: function(map) {
  // Tear down the control.
}

Styling

Leaflet control plugins can be styled with CSS like any other DOM element. Here we add style rules to the my-leaflet-control CSS class that we added to the control’s container div element in the onAdd method:

.my-leaflet-control {
  // Style the Leaflet control plugin here.
}

Leaflet Layer Plugins

While control plugins enhance Leaflet with new functionality for interacting with layers and base maps, layer plugins enable Leaflet to overlay new types of content on base maps and other layers. For example, layer plugins have been created to cluster markers, draw polygons and visualize data.

Creating a Leaflet Layer Plugin

Creating a Leaflet layer plugin follows a similar pattern to creating a control plugin. Leaflet layer plugins are JavaScript classes that extend the Leaflet L.Layer class. A common naming convention is to add the name of the layer plugin to Leaflet’s namespace, which is L. Here, let’s create a plugin control with a class named L.MyLeafletLayer:

L.MyLeafletLayer = L.Layer.extend({
  ...
)}

Agina, the standard Leaflet plugin creation pattern is to implement a factory function that enables the creation of the plugin to be chained with other function calls:

L.myLeafletLayer().addTo(map);

The common convention is to name the factory function after the class of the layer plugin but make the first letter lower case.

L.myLeafletLayer = function(options) {
  return new L.MyLeafletLayer(options);
};

The Layer Plugin Lifecycle

Leaflet calls the following methods of a layer plugin when the layer is added to a Leaflet map:

  1. initialize()
  2. onAdd()

Leaflet calls the initialize method when an instance of a layer plugin is created by calling new directly or by using the factory function:

  1. L.myLeafletLayer()
  2. new L.MyLeafletLayer()

A common layer plugin pattern is to pass the latitude and longitude position of the layer as a key/value pair of the options parameter passed to the initialize method. Recording the position of the layer (line 2) enables the plugin to update the layer correctly when responding to zoom events (described below).

1
2
3
4
initialize: function(options) {
  this._latLng = options.latLng;
  // Continue initializing the layer plugin here.
}

Leaflet calls the onAdd method when the layer is added to the map:

  • layer.addTo(map);
  • map.addControl(layer);

So far, creating a Leaflet layer plugin has been very similar to creating a control plugin. The main difference between layer and control plugins is in the amount of work the onAdd method needs to perform. A common pattern when implementing the onAdd method of a layer plugin is to begin by retaining a reference to the map (line 2) to be able to use it when handling events.

1
2
3
4
5
6
7
8
9
10
11
12
13
onAdd: function(map) {
  this._map = map;

  var layerElementTag = 'div';
  var layerElementClasses = '.my-leaflet-layer leaflet-zoom-hide';
  this._layerElement = L.DomUtil.create(layerElementTag, layerElementClasses);

  // Continue implementing the layer here.

  map.getPanes().overlayPane.appendChild(this._layerElement);
  map.on('viewreset', this._updatePosition, this);
  this._updatePosition();
}

Next, the onAdd method creates the DOM element that will contain the layer (line 4). Although it is common to implement a layer as a DOM element, it is not required. For example, Mike Bostock has a nice example of overlaying an SVG element on a Leaflet map.

Regardless of whether the layer is implemented as a DOM or SVG element, it is important to add the leaflet-zoom-hide CSS class to the element (line 5). Leaflet hides elements with the leaflet-zoom-hide class while the map is zooming to improve performance.

Unlike a Leaflet control plugin, which is added to the map by Leaflet itself after calling map.addControl(control), Leaflet layer plugins must explicitly add themselves to the overlay pane Leaflet provides for plugins (line 10).

After adding itself to a Leaflet map, a layer plugin must start listening for Leaflet’s viewreset event (line 11). Leaflet generates a viewreset event whenever the user zooms the map. The _updatePosition method is our custom viewreset event handler that is responsible for repositioning the layer when the map is zoomed.

Since the _updatePosition method performs the calculation for positioning the layer correctly on the map, we call it now to give the layer its correct initial position (line 12).

Whenever the map is zoomed, the latitude and longitude position of the layer (set when the layer was created) will have different coordinates on the screen. To reposition the layer after a zoom, the _updatePosition method first recalculates the screen coordinates of the layer with the latLngToLayerPoint method (line 2). Next, the screen coordinates of the layer’s DOM element are updated with the new screen coordinates (line 3).

1
2
3
4
_updatePosition: function() {
  var position = this._map.latLngToLayerPoint(this._latLng);
  L.DomUtil.setPosition(this._layerElement, position);
}

Leaflet calls a layer’s onRemove method when the layer is removed from the map:

  • layer.removeFrom(map)
  • map.removeLayer(layer)

Just like the onRemove method of a Leaflet control plugin, the onRemove method of a layer plugin is the place to tear down the layer by releasing resources and removing event listeners. In addition, the layer should remove itself from Leaflet’s overlay pane (line 2) and stop listening for viewreset events (line 3).

1
2
3
4
5
onRemove: function(map) {
  map.getPanes().overlayPane.removeChild(this._layerElement);
  map.off('viewreset', this._updatePosition, this);
  // Continue tearing down the layer here.
}

Styling

Leaflet layer plugins can be styled with CSS like any other DOM or SVG element. Here we add style rules to the my-leaflet-layer CSS class that we added to the layer’s container div element in the onAdd method:

.my-leaflet-layer {
  // Style the Leaflet layer plugin here.
}

Further Reading

The Leaflet documentation has a plugin authoring guide that explains best practices for organising, presenting and demonstrating Leaflet plugin code. The Leaflet plugins page lists a variety of plugins created by the Leaflet community. Most Leaflet plugins are open source and are available on GitHub. Studying the implementation of these plugins is a great way to learn more about creating your own plugins.