Skip to content

Commit 528e8ca

Browse files
authored
fix: add last_object_clicked_count to detect repeated marker clicks (#319)
* fix: add last_object_clicked_count to detect repeated marker clicks Adds a new last_object_clicked_count field that increments on every layer click. This lets apps detect when the same marker is clicked multiple times, which was previously impossible because the lat/lng guard only forwarded changes. Fixes #316 * fix: split fused test_last_object_clicked_count/test_geojson_styles test_last_object_clicked_count was accidentally inserted directly in front of test_geojson_styles with no function boundary, fusing the two and silently deleting test_geojson_styles as standalone regression coverage. Split them back into separate top-level functions. Also drop six noqa: PLC0415 comments from the new test_package.py tests that ruff (pinned via pre-commit) flags as unused-noqa (RUF100), since PLC0415 isn't enabled at that ruff version; every other function-local import in this test suite already omits the noqa.
1 parent b9b0ca7 commit 528e8ca

5 files changed

Lines changed: 107 additions & 0 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import streamlit as st
2+
3+
st.set_page_config(
4+
page_title="streamlit-folium documentation: Last Clicked Count",
5+
page_icon="🔢",
6+
layout="wide",
7+
)
8+
9+
"""
10+
# streamlit-folium: Last Clicked Count
11+
12+
`last_object_clicked_count` increments every time a marker or drawing is clicked,
13+
so app code can detect repeated clicks on the same object even when the lat/lng
14+
has not changed.
15+
"""
16+
17+
with st.echo(code_location="below"):
18+
import folium
19+
import streamlit as st
20+
21+
from streamlit_folium import st_folium
22+
23+
m = folium.Map(location=[39.949610, -75.150282], zoom_start=13)
24+
25+
folium.Marker(
26+
[39.949610, -75.150282], popup="Liberty Bell", tooltip="Liberty Bell"
27+
).add_to(m)
28+
29+
output = st_folium(
30+
m,
31+
width=700,
32+
height=500,
33+
returned_objects=["last_object_clicked", "last_object_clicked_count"],
34+
)
35+
36+
st.write(output)
37+
st.write(f"Click count: {output.get('last_object_clicked_count')}")

streamlit_folium/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ def bounds_to_dict(bounds_list: list[list[float]]) -> dict[str, dict[str, float]
346346
_defaults = {
347347
"last_clicked": None,
348348
"last_object_clicked": None,
349+
"last_object_clicked_count": None,
349350
"last_object_clicked_tooltip": None,
350351
"last_object_clicked_popup": None,
351352
"all_drawings": None,

streamlit_folium/frontend/src/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ var ignore_render = false;
1414
type GlobalData = {
1515
lat_lng_clicked: any
1616
last_object_clicked: any
17+
last_object_clicked_count: number
1718
last_object_clicked_tooltip: string | null
1819
last_object_clicked_popup: string | null
1920
last_active_drawing: any
@@ -94,6 +95,7 @@ function updateComponentValue(map: any) {
9495
let _data = {
9596
last_clicked: wrapLatLng(global_data.lat_lng_clicked),
9697
last_object_clicked: wrapLatLng(global_data.last_object_clicked),
98+
last_object_clicked_count: global_data.last_object_clicked_count,
9799
last_object_clicked_tooltip: global_data.last_object_clicked_tooltip,
98100
last_object_clicked_popup: global_data.last_object_clicked_popup,
99101
all_drawings: global_data.all_drawings,
@@ -200,6 +202,7 @@ function addLayer(e: any) {
200202
function onLayerClick(e: any) {
201203
const global_data = window.__GLOBAL_DATA__
202204
global_data.last_object_clicked = e.latlng || null
205+
global_data.last_object_clicked_count += 1
203206

204207
// Extract tooltip text, guarding against layers that don't fully implement
205208
// the Leaflet Layer interface (e.g. geocoder result markers).
@@ -493,6 +496,7 @@ async function onRender(event: Event) {
493496
window.__GLOBAL_DATA__ = {
494497
lat_lng_clicked: null,
495498
last_object_clicked: null,
499+
last_object_clicked_count: 0,
496500
last_object_clicked_tooltip: null,
497501
last_object_clicked_popup: null,
498502
all_drawings: null,

tests/test_frontend.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,26 @@ def test_responsiveness(page: Page):
266266
page.set_viewport_size({"width": 2000, "height": 2000})
267267

268268

269+
def test_last_object_clicked_count(page: Page):
270+
page.get_by_role("link", name="last clicked count").click()
271+
page.get_by_role("link", name="last clicked count").click()
272+
273+
expect(page).to_have_title("streamlit-folium documentation: Last Clicked Count")
274+
275+
# Initial state: count is 0 before any click (frontend initializes it)
276+
expect(page.get_by_text('"last_object_clicked_count":0')).to_be_visible()
277+
278+
# Click the marker
279+
click_button_or_marker(page)
280+
281+
# After one click, count should be 1
282+
expect(page.get_by_text('"last_object_clicked_count":1')).to_be_visible()
283+
284+
# Click the same marker again; lat/lng does not change, but count should increment
285+
click_button_or_marker(page)
286+
expect(page.get_by_text('"last_object_clicked_count":2')).to_be_visible()
287+
288+
269289
def test_geojson_styles(page: Page):
270290
page.get_by_role("link", name="geojson styles").click()
271291
page.get_by_role("link", name="geojson styles").click()

tests/test_package.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,48 @@ def test_vector_grid():
117117
VectorGridProtobuf(url, "test").add_to(m)
118118
leaflet = _get_map_string(m)
119119
assert "var vector_grid_protobuf_div_1 = L.vectorGrid.protobuf(" in leaflet
120+
121+
122+
def test_last_object_clicked_count_default():
123+
import folium
124+
125+
import streamlit_folium as sf
126+
from streamlit_folium import st_folium
127+
128+
original_component_func = sf._component_func
129+
captured: dict = {}
130+
131+
def fake_component_func(**kwargs):
132+
captured.update(kwargs)
133+
return kwargs.get("default", {})
134+
135+
try:
136+
sf._component_func = fake_component_func
137+
m = folium.Map(location=[0, 0], zoom_start=1)
138+
result = st_folium(m, returned_objects=["last_object_clicked_count"])
139+
assert result["last_object_clicked_count"] is None
140+
assert "last_object_clicked_count" in captured.get("default", {})
141+
finally:
142+
sf._component_func = original_component_func
143+
144+
145+
def test_last_object_clicked_count_not_in_default_when_not_requested():
146+
import folium
147+
148+
import streamlit_folium as sf
149+
from streamlit_folium import st_folium
150+
151+
original_component_func = sf._component_func
152+
captured: dict = {}
153+
154+
def fake_component_func(**kwargs):
155+
captured.update(kwargs)
156+
return kwargs.get("default", {})
157+
158+
try:
159+
sf._component_func = fake_component_func
160+
m = folium.Map(location=[0, 0], zoom_start=1)
161+
result = st_folium(m, returned_objects=["last_object_clicked"])
162+
assert "last_object_clicked_count" not in result
163+
finally:
164+
sf._component_func = original_component_func

0 commit comments

Comments
 (0)