Leaflet / Leaflet.toolbar

Flexible, extensible toolbars for Leaflet maps.
MIT License
197 stars 70 forks source link

Closing submenu on the second click on the button #63

Closed joric closed 3 months ago

joric commented 3 months ago

I'm using 0.4.0-alpha.2 from CDN.

I want to close submenu on a second click on the button. Looks like the second click sends removeHooks and then immediately sends addHooks again and submenu opens again. Is it a bug?

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>

<script src="https://cdn.jsdelivr.net/npm/leaflet-toolbar@0.4.0-alpha.2/dist/leaflet.toolbar.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-toolbar@0.4.0-alpha.2/dist/leaflet.toolbar.min.css">

<style>
html, body, #map { height: 100%; width: 100%; margin: 0; }
</style>
</head>
<body>
<div id="map"></div>
<script>
window.onload = function() {

  const map = L.map('map').setView([51.505, -0.09], 13);
  const tiles = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 19,
    attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
  }).addTo(map);

  MyCustomAction = L.Toolbar2.Action.extend({
    options: {
      toolbarIcon:{
        html: '&#9873;',
        tooltip: 'Files',
      },
      subToolbar: new L.Toolbar2({ 
        actions: [
          L.Toolbar2.Action.extend({
            options:{toolbarIcon:{html:'Go to the Eiffel Tower'}},
            addHooks: function () { 
              map.setView([48.85815, 2.29420], 19);
            }
          }),
        ],
      })
    },
    addHooks: function() {
      console.log('addHooks',this);
    },
    removeHooks: function(e) {
      console.log('removeHooks',this);
    },
  });

  new L.Toolbar2.Control({
      position: 'bottomleft',
      actions: [MyCustomAction],
  }).addTo(map);
}

</script>
</body>
</html>

I also want to close submenus on a map click. This is straightforward but not pretty. Maybe there's a better way?

  // close submenus on map click
  map.on('click', e=>{
    document.querySelectorAll('.leaflet-toolbar-icon').forEach(icon => icon.nextSibling && (icon.nextSibling.style.display = 'none'))
  });

It also seems that closing a submenu is impossible without subclassing because we have to save the action somewhere.

  let subAction = L.Toolbar2.Action.extend({
    initialize:function(map,myAction){this.map=map;this.myAction=myAction;L.Toolbar2.Action.prototype.initialize.call(this);},
    addHooks:function(){ this.myAction.disable(); }
  });
  // create a submenu from this and call subAction.prototype.addHooks.call(this) in a submenu code to close submenu

Do we really need subclassing? Please tell me how to work with the submenus properly.

joric commented 3 months ago

Okay I've managed to do it in subclassing. Here's a gist https://gist.github.com/joric/e325686809ef305a8fd25642d19bbc73

The issue is that toolbar sends consequent disable/enable events on the second click on the same item. We just need to prevent enabling if the disable time was too recent. Also add map handler to close on an outside click, the same way as popups.

  let ImmediateAction = L.Toolbar2.Action.extend({
    initialize: function(map, myAction) {
      L.Toolbar2.Action.prototype.initialize.call(this);
      this.disableTime = Date.now();
      map.on('click', () => {
        this.disable();
      });
    },
    enable: function() {
      if (Date.now() - this.disableTime > 100) {
        L.Toolbar2.Action.prototype.enable.call(this);
      }
    },
    disable: function() {
      if (this._enabled) {
        this.disableTime = Date.now();
      }
      L.Toolbar2.Action.prototype.disable.call(this);
    },
  });

Then use ImmediateAction.extend instead of L.Toolbar2.Action.extend in the toolbar.