How can I get a leaflet.js instance using only a DOM object?
Asked Answered
M

2

8

I'm right now building a custom Knockout.js binding to handle drawing of polygons. In this case the Knockout API only gives me a reference to a DOM object to access whatever it is I need to update. However, it looks like by design leaflet.js wants the user to store the map instance in their implementation. I don't have that option.

Trying this gave me an error: var existingMap = L.map('aMapIDGoesHere')

And the error was: map already initialized.

Any way I can use a DOM element or element ID to access the map instance?

By request here's the custom binding, please note it's a work in progress:

ko.bindingHandlers.leafletDraw = {
  init: function(element, valueAccessor, allBindingsAccessor) {
    var map = L.map(element).setView([40, -90], 3);
    var tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
        attribution: 'OSM',
        minZoom: 2
    }).addTo(map);

    // Initialise the FeatureGroup to store editable layers
    var editableLayers = new L.FeatureGroup();
    map.addLayer(editableLayers);

    // Initialise the draw control and pass it the FeatureGroup of editable layers
    var drawOptions = {
      edit: {
        featureGroup: editableLayers,
        remove: false
      },
      draw: {
        polyline: false,
        circle: false,
        marker: false,
        polygon: {
          allowIntersection: false,
          showArea: true
        }
      }
    }
    var drawControl = new L.Control.Draw(drawOptions);
    map.addControl(drawControl);

    // when a shape is first created
    map.on('draw:created', function (e) {
      var shapeString = $.map(e.layer._latlngs, function(pair) { return pair.lng.toString()+"::"+pair.lat.toString(); }).join(";;;");
      var value = valueAccessor();
      if (ko.isObservable(value)) {
        value(shapeString);
      }

      editableLayers.addLayer(e.layer);

      drawControl.removeFrom(map);
      drawOptions.draw.polygon = false;
      drawOptions.draw.rectangle = false;
      var editControl = new L.Control.Draw(drawOptions);
      map.addControl(editControl);
    });

    // handle when a shape is edited
    map.on('draw:edited', function (e) {
      var editedLayer = e.layers._layers[Object.keys(e.layers._layers)[0]];
      var shapeString = $.map(editedLayer._latlngs, function(pair) { return pair.lng.toString()+"::"+pair.lat.toString(); }).join(";;;");
      var value = valueAccessor();
      if (ko.isObservable(value)) {
        value(shapeString);
      }
    });
  },
  update: function(element, valueAccessor) {
    // need to figure this out since we can't access leaflet params from 
  }
};

Special Note You'll notice that I am converting points into a concatenated string. This is necessary for the time being.

Mistakable answered 28/2, 2014 at 22:10 Comment(2)
Could you add your code ?Header
@Header done. The part where the code matters is under the update property at the bottom. No way to grab a map instance there using just element.Mistakable
C
7

As long as you are sure that the DOM element will not be removed, you could just add it as a subproperty on the DOM element itself. Here's a binding handler using the code on the leaflet front page for setting up the leaflet map:

ko.bindingHandlers.leaflet = {
    init: function(element, valueAccessor){
        var map = L.map(element);
        element.myMapProperty = map;
        L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
            attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
        }).addTo(map);
    },
    update: function(element, valueAccessor){
        var existingMap = element.myMapProperty;
        var value = ko.unwrap(valueAccessor());
        var latitude = ko.unwrap(value.latitude);
        var longitude = ko.unwrap(value.longitude);
        var zoom = ko.unwrap(value.zoom);
        existingMap.setView([latitude, longitude], zoom);
    }
};

To use the binding handler you would just bind like the following:

<div data-bind="leaflet: { latitude: latitudeProperty, longitude: longitudeProperty, zoom: zoomProperty }"></div>

Just ensure that you have also styled the div to ensure it has a height and width. I have written a jsfiddle which uses the above leaflet bindingHandler where you could try it out.

I have only tested this jsfiddle in Internet Explorer 11, Firefox 26.0 and Firefox 27.0.1.

Crossly answered 1/3, 2014 at 1:43 Comment(2)
Wow, never thought to actually store it within the DOM object itself. Nice thinking :) In my use case, if the DOM object is removed then there's no reason to access it further anyways. Thanks for this!Mistakable
You might want to look into ko.utils.domNodeDisposal.addDisposeCallback to ensure you dispose the map object and event listeners if the DOM object is removed. Something like ko.utils.domNodeDisposal.addDisposeCallback(element, function(){ element.myMapProperty.remove(); }); or similar should probably work just fine. (Observe, the code in this comment is completely untested.)Crossly
S
0

noting that in very limited circumstances, this could be a solution: https://mcmap.net/q/1469640/-find-leaflet-map-objects-present-on-page-without-a-variable-reference

window[Object.keys(window).find(key => key.substr(0,3) === "map")];

Read my original post for comments on it's brittleness and limitations, but thought this could be helpful to someone. Thanks!

Scuttlebutt answered 24/3, 2020 at 17:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.