Search code examples
javascriptleafletclojurescriptreagentre-frame

Reagent component in Leaflet Popup - Customize Leaflet Popup


I am building a Clojurescript frontend with Re-Frame and Reagent. At the moment the frontend only contains a map which is created using Leaflet. On click on the map a new marker is set to that position. When clicking on any of those markers a popup is shown.

So far so good. Now the problem is that I want to adjust exactly this Leaflet popup to also contain a delete button, which then should be used to delete the marker again. Here is where I am having problems. The code looks like the following (boiled down to the minimum):

(setup-map!
    (-> js/L
        (.map "mapid")
        (.setView #js [12.34 56.78] 10))]
        (.on map "click" add-marker-to-map! (aget % "latlng" "lat") (aget % 
        "latlng" "lng"))))  

(defn add-marker-to-map!
    [lat long map-object]
    (-> js/L
        (.marker #js [lat long])
        (.addTo map-object)
        (.bindPopup "<div>Hello World!</div>")

This also works out. But I do not want to write inline HTML as a string. I want to write the content of the popup in hiccup syntax and then pass that in some way to ".bindPopup". I want to do this, because the on-click will contain some Clojurescript which I can not put in there if I just write the HTML inline as a string. I tried for example with:
(.bindPopup (reagent.dom.server/render-to-string [:div "Hello World"]))
And this also works fine, but any on-click I put in there is getting lost in translation. I know that this is not working this way (see onClick handler not registering with ReactDOMServer.renderToString or React.js Serverside rendering and Event Handlers).

Therefore I want to find another way of passing my Reagent (which just wraps React) component to Leaflet so that it satisfies the interface of the popup:
setContent(<String|HTMLElement|Function> htmlContent) (https://leafletjs.com/reference-1.3.2.html#popup-setcontent)

So basically I want to render the component into a HTMLElement and not directly into the virtual DOM. I want to call the reagent render function and do not render the result into the app, but get it returned as a HTMLElement that I can pass to Leaflet.

 (reagent/render [hello] (.getElementById js/document "app"))

TLDR: How do I render a Reagent component into a HTMLelement including its on-click function so that I can hand that over to the Leaflet popup?

Any help appreciated.


Solution

  • You can use your own DOM container to render elements:

    (def custom-parent (atom (dom/createDom "div")))
    

    I assume you need more than just one, but for the sake of brevity I'm having one.

    (defn add-marker-to-map!
        [evt]
        (let [evt (js->clj evt :keywordize-keys true)
              {{:keys [lat lng]} :latlng} evt]
            (-> js/L
                (.marker #js [lat lng])
                (.addTo m)
                (.bindPopup (r/render [:div "Marker here"
                                       [:br]
                                       [:button {:on-click (fn [] (js/console.log "clicked"))} "delete me"]]
                                      @custom-parent)))))