Send "fake" event from client-side js to server

I have dynamic content (HTML) in my python dash application that is not generated by dash, rather from an application server. There are some buttons in the content which I would like to wire to send an event back to dash, as if dash had created the buttons.

I found the typescript function that handles callbacks (handleServerSide()) in dash-renderer/src/actions/callbacks.ts. I also see that a typical button click POST payload looks like:

{
    "output":"<id>.<target>",
    "outputs":{"id":"<id>","property":"<target>"},
    "inputs":[{"id":"<button id>","property":"n_clicks","value":<latest value>}],
    "changedPropIds":["<button id>.n_clicks"]
}

However, as handleServerSide() is munged into some random name, I don’t have a way to call it / attach it to the dynamic buttons. While I could certainly send a POST with the right payload to the server / app, the response (whatever is to be rendered) would not be processed without using this function.

Is there a way to create synthetic events on the client side that flow through this mechanism properly, like Shiny.onInputChange()? It should be fairly straightforward to add, I would think, if I understood how handleServerSide() is called.

I used a hack to “solve” the problem. The approach is as follows:

  1. Create an invisible react Input field (known to dash)
  2. Use the normal @app.callback, referencing the ID of the invisible field
  3. Add javascript function to dispatch synthetic react event using the Input field
  4. From within html code can call the javascript function with a payload

Here is the javascript (which should be put in assets dir):

var __rendezvous_count = 0;
var __rendezvouz_setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;


/**
 * Send synthetic react event to application (from client)
 * - detailed here: https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-onchange-event-in-react-js
 *
 * @param payload payload as string
 */
function sendEventToApp(payload) {
    var rendezvous = document.getElementById("ExternalEvent");
    var newvalue = payload + ":" + __rendezvous_count++;

    __rendezvouz_setter.call(rendezvous, newvalue);

    var ev = new Event('input', { bubbles: true });
    rendezvous.dispatchEvent(ev);
}

Here is a simple contrived test:

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
import dash_dangerously_set_inner_html

app = dash.Dash(__name__, external_stylesheets = [dbc.themes.UNITED])


def ExternalEvents():
    return dbc.Input(type='text', id='ExternalEvent', placeholder='', style={'visibility': 'hidden'})

def HTML(html):
    return dash_dangerously_set_inner_html.DangerouslySetInnerHTML(html)

@app.callback(Output('target', 'children'), [Input('ExternalEvent', 'value')])
def update_input(value):
    return "Click Me" if value is None else value

app.layout = dbc.Container([
        html.H2("Click Me", id='target'),
        html.Div([
            HTML("""<button onclick='sendEventToApp("button1")' class="btn">test</button>""")
        ]),
        ExternalEvents(),
    ])

app.run_server()
1 Like