I have the following callback
@app.callback(Output("full_address", "data"),
Input("corrected_address", "value"))
def store_address(inputdata):
print(inputdata)
return inputdata
which is tied to the element created by
dbc.Input(id="corrected_address")
This element uses a Google Places auto complete element:
var input = document.getElementById('corrected_address');
var autocomplete = new google.maps.places.Autocomplete(input);
autocomplete.setFields(['address_component', 'geometry', 'name']);
The auto complete shows up as expected.
However, when completed, the callback is not triggered
Updating the field by adding or removing a character updates the value as expected.
I feel like I'm missing something simple here. How can I get the callback to trigger?
I'm using dash and dash-bootstrap-components, Python 3.10.12.
I've found a solution that works quite nicely in my opinion. Due to Dash ignoring changes to values from JavaScript, going as far as to ignore manually issued change events, dash_extensions EventListener
can be used alongside a custom event and a serverside callback to update the fields value appropriately on the backend.
from dash import Dash, html, dcc, clientside_callback
from dash_extensions import EventListener
from dash.exceptions import PreventUpdate
from dash.dependencies import Input, Output, State, ALL
import dash_bootstrap_components as dbc
GAPI_STRING = ""
external_scripts = [
'https://maps.googleapis.com/maps/api/js?key='+GAPI_STRING+'&libraries=places',
]
app = Dash(
__name__,
suppress_callback_exceptions=True,
external_stylesheets=[dbc.themes.BOOTSTRAP],
external_scripts=external_scripts,
)
# The event used to pass the value of the autocomplete field from the client to the server
manual_notify = {"event": "autocomplete_finished", "props": ["srcElement.value"]}
app.layout = [
html.Div([html.Label("Current Address:", id="address_label")], id="display_div"),
html.Div([
EventListener(
[
dbc.Input(id="auto_complete_field")
], events=[manual_notify], logging=True, id="event_listener"
),
], id="auto_complete_div")
]
clientside_callback(
"""
// Used to initiate autocomplete after the Dash form is created
// This function requires the element to be present,
// and if it were in a normal external script, it would not run
// and auto complete would not initialize.
// This callback essentially takes in the element when it's created
// Runs the autocomplete setup functions, and returns the element as
// it is.
function(facade1) {
// setup autcomplete
var input = document.getElementById('auto_complete_field');
var autocomplete = new google.maps.places.Autocomplete(input);
autocomplete.setFields(['address_component', 'geometry', 'name']);
// dispatch the "autocomplete_finished" event when "place_changed" is dispatched.
autocomplete.addListener('place_changed', () => input.dispatchEvent(new Event("autocomplete_finished")));
return facade1;
}
""",
Output("auto_complete_div", "children"),
Input("auto_complete_div", "children")
)
# When the autocomplete_finished event is dispatched
# this callback will handle it, and outputs the full
# address to the autocomplete fields value property,
# syncing the backend value with the front end value
@app.callback(
Output("auto_complete_field", "value"),
Input("event_listener", "n_events"),
State("event_listener", "event"))
def store_address(n_events, event):
if event is None:
raise PreventUpdate()
return event["srcElement.value"]
# This updates the current address label using the
# autocomplete fields value property as the input,
# proving that the internal value is the same as
# the one in the clients input field.
@app.callback(Output("address_label", "children"), Input("auto_complete_field", "value"))
def print_address(field_value):
if field_value == None:
raise PreventUpdate()
return "Current Address: " + field_value
if __name__ == "__main__":
app.run(debug=True)
I hope this helps somebody! This was a doozy to figure out.