Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make popup clickable and interactable #11

Closed
scherler opened this issue Feb 12, 2015 · 16 comments
Closed

Make popup clickable and interactable #11

scherler opened this issue Feb 12, 2015 · 16 comments

Comments

@scherler
Copy link

Hi Paul,

I am trying to use clickable elements in popups, but without success.

For example imagine the following:

    <Marker position={position}>
      <Popup>
          <button onClick={this.bus}>thorsten</button>
        </Popup>
    </Marker>

Now if you click on the button there is nothing happening (at least there should be a complain about "bus" not defined. On a side note I tried as well with "onclick" and as well with onClick="this.bus()"

Now when I patch the marker like:

--- a/src/Marker.js
+++ b/src/Marker.js
@@ -16,6 +16,9 @@ module.exports = React.createClass({
   componentWillMount() {
     var {map, position, ...props} = this.props;
-    this._leafletElement = Leaflet.marker(position, props);
+    var marker = Leaflet.marker(position, props);
+    if (this.props.onClick) {
+      marker.on('click', this.props.onClick);
+    }
+   this._leafletElement = marker;
   },

And do

<Marker position={position}  onClick={this.bus}"/>

Then it is working as expected but I have no popup.

To bring back the popup and not having React.renderToStaticMarkup (which is in the Popup.js) remove all onClicks or logic I use, I came up with the following:

--- a/src/Marker.js
+++ b/src/Marker.js
@@ -16,6 +16,9 @@ module.exports = React.createClass({
   componentWillMount() {
     var {map, position, ...props} = this.props;
-    this._leafletElement = Leaflet.marker(position, props);
+    var marker = Leaflet.marker(position, props);
+    if (this.props.onClick) {
+      marker.on('click', this.props.onClick);
+    }
+   this._leafletElement = marker;
+    var popup = this.props.popup;
+    if (popup) {
+      var LPopup = Leaflet.popup().setContent(popup);
+      marker.bindPopup(LPopup);
+    }
+    this._leafletElement = marker;
   },

and in my calling code I have:

   function bus(){
     console.log("ssssssssserererererssss")
   }

   Leaflet.thorsten={};
   Leaflet.thorsten.bus = bus;
...
    var  xxx = '<div><h2>bus</h2><button onclick="L.thorsten.bus()">thorsten</button></div>';
     <Marker position={position} popup={xxx}"/>

This finally works, but it really feels like a workaround more then the way to go.

Do you know a better way?

@PaulLeCam
Copy link
Owner

Hi,
I use React.renderToStaticMarkup() because Leaflet sets the content of the Popup by manipulating the DOM itself (https://github.com/Leaflet/Leaflet/blob/master/src/layer/Popup.js#L172), so at this point it is not possible to use React elements.

Instead of having to patch react-leaflet, I think another way you could do it is to instantiate a Leaflet Popup and attach it to the Marker in your application instead of using the <Popup> element, this way you would have full control over it.

The best solution may be to render an empty element with a specific ID as the content of the Popup so that Leaflet inserts it in the DOM, and then call React.render() on this element, but I've never tried this so I have no idea how it would behave. I'll try this when I can find some time.

@scherler
Copy link
Author

Thanks Paul, I will now look into your suggestion and let you know.

scherler pushed a commit to codebusters/react-leaflet that referenced this issue Feb 18, 2015
-Creating a Mixin which allows to use React content in leaflet popups
@scherler
Copy link
Author

Hi Pau, I came up with a Mixin which is capable of what you describe in the earlier comment, you may want to check the pull request.

@PaulLeCam
Copy link
Owner

Hi,
Great if that works for you.
I think this kind of code should be in the application, not the library as it introduces specific handlers, that goes against the purpose of this library to simply wrap Leaflet in React components.

@scherler
Copy link
Author

ok, I understand

@barbalex
Copy link

barbalex commented Feb 8, 2017

Hm. I must admit that I have not understood how @scherler achieved this.
I have the same need.
And I can imagine many people would like to make interactive popups.

Is there some example of how to do this?

@prabuvenkat
Copy link

+1 for this feature

@RoryShively
Copy link

+1

@scherler
Copy link
Author

@barbalex @prabuvenkat @RoryShively you can find the PR #13

I have to admit that I did not looked at this since back then and I guess that would be better implemented as ComposedComponent

@alfredoxyanez
Copy link

Im trying yo add a D3 graph into the pop up. Is this possible ?

@tobiaskauer
Copy link

@ayanez17 should work, since the popup is a DOM element:

var svg = d3.select(".leaflet-popup-content").append("svg")

@roman7722
Copy link

roman7722 commented Sep 18, 2021

Perhaps this example will help codesandbox

import React, { Component } from "react";
import ReactDOM from "react-dom";
import ReactDOMServer from "react-dom/server";
import * as L from "leaflet";
import "./styles.css";

const GEO_POSITION = [51.5, 0];

const CustomReactPopup = () => {
  return (
    <div style={{ fontSize: "14px" }}>
      <p>CustomReactPopup</p>
      <button id="popup-button-id">Click</button>
    </div>
  );
};

class App extends Component {
  state = {
    center: GEO_POSITION,
    zoom: 13
  };

  clickHandler = () => {
    console.count("click");
  };

  gtt = () => {
    const buttonEl = document.getElementById("popup-button-id");

    if (buttonEl) {
      buttonEl.addEventListener("click", this.clickHandler);
    }
  };

  gtt2 = () => {
    const buttonEl = document.getElementById("popup-button-id");

    if (buttonEl) {
      buttonEl.removeEventListener("click", this.clickHandler);
    }
  };

  componentDidMount() {
    var map = L.map("map").setView(GEO_POSITION, 13);

    L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
      attribution:
        '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
    }).addTo(map);

    L.marker(GEO_POSITION)
      .addEventListener("popupopen", this.gtt)
      .addEventListener("popupclose", this.gtt2)
      .addTo(map)
      .bindPopup(ReactDOMServer.renderToString(<CustomReactPopup />))

      .openPopup();
  }

  render() {
    return (
      <div>
        <div id="map" />
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

@Tmfwang
Copy link

Tmfwang commented Oct 28, 2021

const buttonEl = document.getElementById("popup-button-id");

    if (buttonEl) {
      buttonEl.addEventListener("click", this.clickHandler);
    }

This really helped me out! Thanks :D

@imran-5
Copy link

imran-5 commented Jan 27, 2022

Try this method

const popupNode = document.createElement('div');
ReactDOM.render(<div onClick={()=>{console.log("test")}}>Test</div>, popupNode)
L.popup()
.setLatLng(latlng)
.setContent(popupNode)
.openOn(map);

@GhadaAJIMI
Copy link

anyone know how we do this in angular?

@alateefah
Copy link

alateefah commented Nov 15, 2022

Perhaps this example will help codesandbox

import React, { Component } from "react";
import ReactDOM from "react-dom";
import ReactDOMServer from "react-dom/server";
import * as L from "leaflet";
import "./styles.css";

const GEO_POSITION = [51.5, 0];

const CustomReactPopup = () => {
  return (
    <div style={{ fontSize: "14px" }}>
      <p>CustomReactPopup</p>
      <button id="popup-button-id">Click</button>
    </div>
  );
};

class App extends Component {
  state = {
    center: GEO_POSITION,
    zoom: 13
  };

  clickHandler = () => {
    console.count("click");
  };

  gtt = () => {
    const buttonEl = document.getElementById("popup-button-id");

    if (buttonEl) {
      buttonEl.addEventListener("click", this.clickHandler);
    }
  };

  gtt2 = () => {
    const buttonEl = document.getElementById("popup-button-id");

    if (buttonEl) {
      buttonEl.removeEventListener("click", this.clickHandler);
    }
  };

  componentDidMount() {
    var map = L.map("map").setView(GEO_POSITION, 13);

    L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
      attribution:
        '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
    }).addTo(map);

    L.marker(GEO_POSITION)
      .addEventListener("popupopen", this.gtt)
      .addEventListener("popupclose", this.gtt2)
      .addTo(map)
      .bindPopup(ReactDOMServer.renderToString(<CustomReactPopup />))

      .openPopup();
  }

  render() {
    return (
      <div>
        <div id="map" />
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

With this implementation, if there are multiple markers, one has to close popup via the x button for the event listener to be bounded to the next click. I modified your code to show this below:

import React, { Component } from "react";
import ReactDOM from "react-dom";
import ReactDOMServer from "react-dom/server";
import * as L from "leaflet";
import "./styles.css";

const GEO_POSITION = [51.5, 0];
const GEO_POSITION2 = [51.5, 0.1];

const CustomReactPopup = () => {
  return (
    <div style={{ fontSize: "14px" }}>
      <p>CustomReactPopup</p>
      <button id="popup-button-id">Click</button>
    </div>
  );
};

class App extends Component {
  state = {
    center: GEO_POSITION,
    zoom: 13
  };

  clickHandler = () => {
    console.count("click");
  };

  gtt = () => {
    const buttonEl = document.getElementById("popup-button-id");

    if (buttonEl) {
      buttonEl.addEventListener("click", this.clickHandler);
    }
  };

  gtt2 = () => {
    const buttonEl = document.getElementById("popup-button-id");

    if (buttonEl) {
      buttonEl.removeEventListener("click", this.clickHandler);
    }
  };

  componentDidMount() {
    var map = L.map("map").setView(GEO_POSITION, 13);

    L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
      attribution:
        '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
    }).addTo(map);

    L.marker(GEO_POSITION)
      .addEventListener("popupopen", this.gtt)
      .addEventListener("popupclose", this.gtt2)
      .addTo(map)
      .bindPopup(ReactDOMServer.renderToString(<CustomReactPopup />))

      //.openPopup();

      L.marker(GEO_POSITION2)
      .addEventListener("popupopen", this.gtt)
      .addEventListener("popupclose", this.gtt2)
      .addTo(map)
      .bindPopup(ReactDOMServer.renderToString(<CustomReactPopup />))

      //.openPopup();
  }

  render() {
    return (
      <div>
        <div id="map" />
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Figured it out!
I used a unique id for each marker

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests