Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/advanced_guide/customize_javascript_and_css.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,22 @@ rt.add_js_link("dayjs", "https://cdn.jsdelivr.net/npm/dayjs@1.11.10/dayjs.min.js
rt.add_to(m)
m
```

## Referring to other map objects in JsCode
Inside a `JsCode` block you often need the Javascript variable of another folium object, such as a marker or a layer. You can write `%(name)s` placeholders in the code and pass the objects as keyword arguments. Folium objects are replaced by their Javascript variable name when the map is rendered, other `JsCode` objects by their code, and any other value is inserted as is.

When you pass parameters, write a literal `%` in the code as `%%`.

```{code-cell} ipython3
from folium.utilities import JsCode

m = folium.Map(location=[45, -30], zoom_start=4)
marker = folium.Marker([45, -30], popup="Hello!").add_to(m)

m.on(click=JsCode("""
function(e) {
%(marker)s.setLatLng(e.latlng).openPopup();
}
""", marker=marker))
m
```
58 changes: 54 additions & 4 deletions folium/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,13 +433,63 @@ def get_and_assert_figure_root(obj: Element) -> Figure:


class JsCode:
"""Wrapper around Javascript code."""
"""Wrapper around Javascript code.

def __init__(self, js_code: Union[str, "JsCode"]):
Parameters
----------
js_code: str or JsCode
The Javascript code. It may contain ``%(name)s`` placeholders that are
filled in with the keyword arguments.
**params:
Values for the placeholders in `js_code`. A folium (or branca) element
is replaced by its Javascript variable name, a JsCode object by its
code, and any other value is formatted with the ``%`` operator.
The placeholders are filled in when the code is rendered.

When parameters are given, a literal ``%`` in the code must be written as
``%%``. Without parameters the code is used as is.

Examples
--------
>>> m = folium.Map()
>>> marker = folium.Marker([45, -30]).add_to(m)
>>> handler = JsCode(
... '''
... function(e) {
... %(marker)s.openPopup();
... }
... ''',
... marker=marker,
... )
"""

def __init__(self, js_code: Union[str, "JsCode"], **params: Any):
if isinstance(js_code, JsCode):
self.js_code: str = js_code.js_code
self._template: str = js_code._template
self.params: dict[str, Any] = {**js_code.params, **params}
else:
self.js_code = js_code
self._template = js_code
self.params = params

@property
def js_code(self) -> str:
if not self.params:
return self._template
return self._template % {
key: self._resolve(value) for key, value in self.params.items()
}

@js_code.setter
def js_code(self, value: str) -> None:
self._template = value

@staticmethod
def _resolve(value: Any) -> Any:
if isinstance(value, JsCode):
return value.js_code
if isinstance(value, Element):
return value.get_name()
return value

def __str__(self):
return self.js_code
Expand Down
49 changes: 49 additions & 0 deletions tests/test_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,55 @@ def test_js_code_init_js_code():
assert isinstance(js_code_2.js_code, str)


def test_js_code_without_params_is_not_formatted():
js_code = JsCode("var x = 10 % 3; var y = '%(name)s';")
assert js_code.js_code == "var x = 10 % 3; var y = '%(name)s';"


def test_js_code_params_element():
m = Map()
marker = Marker([0, 0]).add_to(m)
js_code = JsCode("%(marker)s.openPopup();", marker=marker)
assert js_code.js_code == f"{marker.get_name()}.openPopup();"
assert str(js_code) == js_code.js_code


def test_js_code_params_js_code_and_plain_values():
inner = JsCode("function(e) { return %(n)s; }", n=5)
js_code = JsCode(
"var f = %(func)s; var s = '%(text)s'; var p = 10 %% 3;",
func=inner,
text="hello",
)
assert js_code.js_code == (
"var f = function(e) { return 5; }; var s = 'hello'; var p = 10 % 3;"
)


def test_js_code_params_merged_when_wrapping_js_code():
js_code = JsCode("%(a)s %(b)s", a=1, b=2)
js_code_2 = JsCode(js_code, b=3)
assert js_code_2.js_code == "1 3"
assert js_code.js_code == "1 2"


def test_js_code_params_resolved_at_render_time():
m = Map()
marker = Marker([0, 0])
js_code = JsCode("%(marker)s", marker=marker)
marker._name = "SomethingElse"
marker.add_to(m)
assert js_code.js_code == marker.get_name()


def test_js_code_params_rendered_in_map():
m = Map()
marker = Marker([0, 0]).add_to(m)
m.on(click=JsCode("function(e) { %(marker)s.openPopup(); }", marker=marker))
html = m.get_root().render()
assert f"{marker.get_name()}.openPopup();" in html


@pytest.mark.parametrize(
"value,expected",
[
Expand Down
Loading