While most of the code in a Dash app is Python, i tend to always end up writing at least a few lines of JavaScript. Either as client side callbacks, or for mapping of Dash arguments (string, numbers, etc.) into the data types expected by the underlying React component (most often function handles). Looking for a way to avoid this, i stumbled upon the transcrypt project, which transpiles Python to JavaScript.
After playing around with it a little (it works surprising good), i decided to write some bindings to Dash (they are available in `dash-transcrypt==0.0.6) to ease the integration. I am targeting two main usecases,
- Clientside callbacks
- Functional properties (e.g. props of Dash components that takes a function as the value)
Clientside callbacks
As a small example, consider a (simple) calculator that adds two numbers. The clientside function(s) must by placed in a separate file, say calculator_csf.py
,
def add(a, b):
return a + b
Before the add
function can be used as a clientside callback, the calculator_csf
module must be passed through the to_clientside_functions
function. In addition to transpiling the module into JavaScript, it replaces the functional attributes of the module with appropriate ClientsideFunction
objects so that they can be used in clientside callbacks,
import calculator_csf
from dash_extensions.transpile import module_to_clientside_functions, inject_js
...
js_index = module_to_clientside_functions(calculator_csf) # the call generates the js
app.clientside_callback(calculator_csf.add, ...) # pass the functions as you would normally do
inject_js(app, js_index) # the call adds the js to the app
The to_clientside_functions
returns the path to a JavaScript index file, which must be made available by the app (that’s what inject_js
does). For completeness, here is the full example app,
import dash
import dash_core_components as dcc
import dash_html_components as html
import calculator_csf
from dash.dependencies import Output, Input
from dash_extensions.transpile import module_to_clientside_functions, inject_js
# Create example app.
app = dash.Dash()
app.layout = html.Div([
dcc.Input(id="a", value=2, type="number"), html.Div("+"),
dcc.Input(id="b", value=2, type="number"), html.Div("="), html.Div(id="c"),
])
# Create clientside callback.
inject_js(app, module_to_clientside_functions(calculator_csf))
app.clientside_callback(calculator_csf.add, Output("c", "children"), [Input("a", "value"), Input("b", "value")])
if __name__ == '__main__':
app.run_server()
Functional properties
If a components supports translating the full path of a function (a string) to the function itself, dash-transcrypt can help build these strings. An example of a component that supports this flow is the GeoJSON
component in dash-leaflet. The relevant function(s) must be placed in a separate module (file), say scatter_js.py
,
def point_to_layer(feature, latlng, context):
return L.circleMarker(latlng, dict(radius=feature.properties.value*10))
Before the function(s) can be assigned as a property, the module must be passed through the module_to_props
function. In addition to transpiling the module into JavaScript, it replaces the function attributes of the module with the appropriate strings,
import scatter_js as sjs
import dash_leaflet as dl
from dash_transcrypt import inject_js, module_to_props
...
js = module_to_props(sjs)
geojson = dl.GeoJSON(data=data, options=dict(pointToLayer=sjs.point_to_layer)) # pass function as prop
...
inject_js(app, js)
Have others played with something like this? What is your experience?
EDIT: I have added the second use case and created a separate dash-transcrypt package