ipyleafletpy-shiny

How to update a pyshiny text input with title from clicked marker in ipyleaflet map


I'd like to update a text input in a pyshiny app each time a marker is clicked in an ipyleaflet map widget using the marker's title value. I have been using the marker.on_click() callback to get the title (station_id in this case) from the clicked marker, but I am struggling to dynamically change the text input box with the title text.

A basic working example that I have tried is shown below. The title of each marker is printed to screen from within the callback() function each time it is clicked. However, I can't figure out how to update the ui.input_text's value. The Reactive.Effect function does not seem to be called at all and I'm not sure why. What do I need to change here to update the value in the text input?

from shiny import App, reactive, run_app, ui
from shinywidgets import output_widget, register_widget
import ipyleaflet as ipyl


app_ui = ui.page_fluid(
    {"class": "p-4"},
    ui.row(
        ui.column(
            4,
            ui.input_text("station_id", "Station ID:", value="Click on a station marker"),
        ),
        ui.column(
            8,
            output_widget("map_widget")
        ),
    ),
)


def get_station_id(_marker):
    def callback(*args, **kwargs):
        print(_marker.title)
        return _marker.title
    return callback


def add_stations_to_map(_map, stations):
    markers = []
    for station in stations:
        marker = ipyl.Marker(location=station[0], draggable=False, title=station[1])
        _map.add(marker)
        markers.append(marker)
    return markers


def server(input, output, session):
    # Initialize and display when the session starts (1)
    _map = ipyl.Map(center=(-33.8, 151), zoom=8, scroll_wheel_zoom=True)
    # Add a distance scale
    _map.add(ipyl.leaflet.ScaleControl(position="bottomleft"))
    register_widget("map_widget", _map)

    # add markers and station id clik function
    stations = [
        [(-33.8, 151), "station_1"],
        [(-33.8, 150), "station_2"],
    ]
    markers = add_stations_to_map(_map, stations)
    clicked_marker = "Click on a station marker"
    for marker in markers:
        clicked_marker = marker.on_click(get_station_id(marker))

    @reactive.Effect
    def _():
        ui.update_text("station_id", value=clicked_marker)


app = App(app_ui, server, debug=True)
run_app(app, launch_browser=True, port=0)

Solution

  • You can use a reactive.Value to set your text:

    clicked_marker_value = reactive.Value("Click on a station marker")
    def get_station_id(_marker):
        def callback(*args, **kwargs):
            print(_marker.title)
            clicked_marker_value.set(_marker.title)
            return _marker.title
        return callback
    
    @reactive.Effect
        def _():
            ui.update_text("station_id", value=clicked_marker_value())