Skip to content

Commit 9fb5f2a

Browse files
hansthenConengmo
andauthored
Add Evented base class (#1959)
* Add EventHandler to layer object In combination with JsCode this makes it easier for users to add `on` method calls for event handling without extending Folium itself. The functionality was inspired by PR #1866 by @yschopfer19. The PR was not accepted yet, because of concerns with code duplication. In the approach taken in the current PR, #1866 would not be necessary anymore, as the requested changes could be added completely in client code space. * Make realtime inherit from Layer * Changes after review comments by conengmo * Updates after review comments * Add extra docstring line * Add Evented class In Leaflet Evented is the parent class of both `L.Map` and `L.Layer`. It adds the `on` method which can be used to add event handlers to a leaflet object. * Update folium/map.py Co-authored-by: Frank Anema <33519926+Conengmo@users.noreply.github.com> * Update folium/map.py Co-authored-by: Frank Anema <33519926+Conengmo@users.noreply.github.com> * As requested in review comment --------- Co-authored-by: Frank Anema <33519926+Conengmo@users.noreply.github.com>
1 parent b80e7e9 commit 9fb5f2a

File tree

5 files changed

+118
-5
lines changed

5 files changed

+118
-5
lines changed

docs/reference.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Utilities
3535
---------------------
3636

3737
.. autoclass:: folium.utilities.JsCode
38+
.. autoclass:: folium.elements.EventHandler
3839

3940

4041
Plugins

folium/elements.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from branca.element import CssLink, Element, Figure, JavascriptLink, MacroElement
44
from jinja2 import Template
55

6+
from folium.utilities import JsCode
7+
68

79
class JSCSSMixin(Element):
810
"""Render links to external Javascript and CSS resources."""
@@ -46,6 +48,80 @@ def _add_link(self, name: str, url: str, default_list: List[Tuple[str, str]]):
4648
default_list.append((name, url))
4749

4850

51+
class EventHandler(MacroElement):
52+
'''
53+
Add javascript event handlers.
54+
55+
Examples
56+
--------
57+
>>> import folium
58+
>>> from folium.utilities import JsCode
59+
>>>
60+
>>> m = folium.Map()
61+
>>>
62+
>>> geo_json_data = {
63+
... "type": "FeatureCollection",
64+
... "features": [
65+
... {
66+
... "type": "Feature",
67+
... "geometry": {
68+
... "type": "Polygon",
69+
... "coordinates": [
70+
... [
71+
... [100.0, 0.0],
72+
... [101.0, 0.0],
73+
... [101.0, 1.0],
74+
... [100.0, 1.0],
75+
... [100.0, 0.0],
76+
... ]
77+
... ],
78+
... },
79+
... "properties": {"prop1": {"title": "Somewhere on Sumatra"}},
80+
... }
81+
... ],
82+
... }
83+
>>>
84+
>>> g = folium.GeoJson(geo_json_data).add_to(m)
85+
>>>
86+
>>> highlight = JsCode(
87+
... """
88+
... function highlight(e) {
89+
... e.target.original_color = e.layer.options.color;
90+
... e.target.setStyle({ color: "green" });
91+
... }
92+
... """
93+
... )
94+
>>>
95+
>>> reset = JsCode(
96+
... """
97+
... function reset(e) {
98+
... e.target.setStyle({ color: e.target.original_color });
99+
... }
100+
... """
101+
... )
102+
>>>
103+
>>> g.add_child(EventHandler("mouseover", highlight))
104+
>>> g.add_child(EventHandler("mouseout", reset))
105+
'''
106+
107+
_template = Template(
108+
"""
109+
{% macro script(this, kwargs) %}
110+
{{ this._parent.get_name()}}.on(
111+
{{ this.event|tojson}},
112+
{{ this.handler.js_code }}
113+
);
114+
{% endmacro %}
115+
"""
116+
)
117+
118+
def __init__(self, event: str, handler: JsCode):
119+
super().__init__()
120+
self._name = "EventHandler"
121+
self.event = event
122+
self.handler = handler
123+
124+
49125
class ElementAddToElement(MacroElement):
50126
"""Abstract class to add an element to another element."""
51127

folium/folium.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
import webbrowser
88
from typing import Any, List, Optional, Sequence, Union
99

10-
from branca.element import Element, Figure, MacroElement
10+
from branca.element import Element, Figure
1111
from jinja2 import Template
1212

1313
from folium.elements import JSCSSMixin
14-
from folium.map import FitBounds, Layer
14+
from folium.map import Evented, FitBounds, Layer
1515
from folium.raster_layers import TileLayer
1616
from folium.utilities import (
1717
TypeBounds,
@@ -79,7 +79,7 @@ def __init__(self, no_touch=False, disable_3d=False):
7979
self.disable_3d = disable_3d
8080

8181

82-
class Map(JSCSSMixin, MacroElement):
82+
class Map(JSCSSMixin, Evented):
8383
"""Create a Map with Folium and Leaflet.js
8484
8585
Generate a base map of given width and height with either default

folium/map.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
from branca.element import Element, Figure, Html, MacroElement
1111
from jinja2 import Template
1212

13-
from folium.elements import ElementAddToElement
13+
from folium.elements import ElementAddToElement, EventHandler
1414
from folium.utilities import (
15+
JsCode,
1516
TypeBounds,
1617
TypeJsonValue,
1718
camelize,
@@ -21,7 +22,23 @@
2122
)
2223

2324

24-
class Layer(MacroElement):
25+
class Evented(MacroElement):
26+
"""The base class for Layer and Map
27+
28+
Adds the `on` method for event handling capabilities.
29+
30+
See https://leafletjs.com/reference.html#evented for
31+
more in depth documentation. Please note that we have
32+
only added the `on(<Object> eventMap)` variant of this
33+
method using python keyword arguments.
34+
"""
35+
36+
def on(self, **event_map: JsCode):
37+
for event_type, handler in event_map.items():
38+
self.add_child(EventHandler(event_type, handler))
39+
40+
41+
class Layer(Evented):
2542
"""An abstract class for everything that is a Layer on the map.
2643
It will be used to define whether an object will be included in
2744
LayerControls.

tests/test_features.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
import folium
1515
from folium import Choropleth, ClickForMarker, GeoJson, Map, Popup
16+
from folium.elements import EventHandler
17+
from folium.utilities import JsCode
1618

1719

1820
@pytest.fixture
@@ -283,6 +285,23 @@ def test_geojson_empty_features_with_styling():
283285
m.get_root().render()
284286

285287

288+
def test_geojson_event_handler():
289+
"""Test that event handlers are properly generated"""
290+
m = Map()
291+
data = {"type": "FeatureCollection", "features": []}
292+
geojson = GeoJson(data, style_function=lambda x: {}).add_to(m)
293+
fn = JsCode(
294+
"""
295+
function f(e) {
296+
console.log("only for testing")
297+
}
298+
"""
299+
)
300+
geojson.add_child(EventHandler("mouseover", fn))
301+
rendered = m.get_root().render()
302+
assert fn.js_code in rendered
303+
304+
286305
def test_geometry_collection_get_bounds():
287306
"""Assert #1599 is fixed"""
288307
geojson_data = {

0 commit comments

Comments
 (0)