From de5231651dd0caebe23e2e781eb9b4e902ac8dcf Mon Sep 17 00:00:00 2001 From: KhadeerBasha1232 Date: Wed, 4 Nov 2026 18:21:15 +0530 Subject: [PATCH] Add template parameters to JsCode JsCode now accepts keyword arguments that fill %(name)s placeholders. Elements are replaced by their Javascript variable name, JsCode objects by their code, and other values are formatted as usual. Placeholders are filled in at render time; code without parameters is used as is. Closes #2121 Co-Authored-By: Claude Opus 5.5 --- .../customize_javascript_and_css.md | 19 ++++++ folium/utilities.py | 58 +++++++++++++++++-- tests/test_utilities.py | 49 ++++++++++++++++ 3 files changed, 122 insertions(+), 4 deletions(-) diff --git a/docs/advanced_guide/customize_javascript_and_css.md b/docs/advanced_guide/customize_javascript_and_css.md index 491c8d5d29..62626e0a6a 100644 --- a/docs/advanced_guide/customize_javascript_and_css.md +++ b/docs/advanced_guide/customize_javascript_and_css.md @@ -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 +``` diff --git a/folium/utilities.py b/folium/utilities.py index e9807ad91d..bade4faf2f 100644 --- a/folium/utilities.py +++ b/folium/utilities.py @@ -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 diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 26d346ca39..07e1b6f3a5 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -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", [