Skip to content

Commit f2c56d1

Browse files
authored
🔥 clientside callback interface (#672)
* 🐎 clientside callback interface see associated PR & examples in plotly/dash-renderer#143 * 📝 don't overload `callback` with `clientside` and rename `client_function` to `clientside_function * ✅ straighten up the pylint directives as per #672 (comment) * ⚡ No clientside function = `None` * 📝 CHANGELOG * 📝 pylint * ⚡ unused files
1 parent bb5ed70 commit f2c56d1

File tree

3 files changed

+84
-12
lines changed

3 files changed

+84
-12
lines changed

CHANGELOG.md

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
## Unreleased
2-
32
### Added
4-
5-
- add `dev_tools_ui` in `<script id="_dash-config" type="application/json">`,
6-
so renderer can know if it should enable the UI. [#676](https://github.com/plotly/dash/pull/676)
7-
3+
- Support for "Clientside Callbacks" - an escape hatch to execute your callbacks in JavaScript instead of Python [#672](https://github.com/plotly/dash/pull/672)
4+
- Added `dev_tools_ui` config flag in `app.run_server` (serialized in `<script id="_dash-config" type="application/json">`)
5+
to display or hide the forthcoming Dev Tools UI in Dash's front-end (dash-renderer). [#676](https://github.com/plotly/dash/pull/676)
86

97
## [0.40.0] - 2019-03-25
108
### Changed

dash/dash.py

+64-1
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,7 @@ def dependencies(self):
633633
'output': k,
634634
'inputs': v['inputs'],
635635
'state': v['state'],
636+
'clientside_function': v.get('clientside_function', None)
636637
} for k, v in self.callback_map.items()
637638
])
638639

@@ -936,6 +937,68 @@ def _validate_value(val, index=None):
936937
else:
937938
_validate_value(output_value)
938939

940+
# pylint: disable=dangerous-default-value
941+
def clientside_callback(
942+
self, clientside_function, output, inputs=[], state=[]):
943+
"""
944+
Create a callback that updates the output by calling a clientside
945+
(JavaScript) function instead of a Python function.
946+
947+
Unlike `@app.calllback`, `clientside_callback` is not a decorator:
948+
it takes a
949+
`dash.dependencies.ClientsideFunction(namespace, function_name)`
950+
argument that describes which JavaScript function to call
951+
(Dash will look for the JavaScript function at
952+
`window[namespace][function_name]`).
953+
954+
For example:
955+
```
956+
app.clientside_callback(
957+
ClientsideFunction('my_clientside_library', 'my_function'),
958+
Output('my-div' 'children'),
959+
[Input('my-input', 'value'),
960+
Input('another-input', 'value')]
961+
)
962+
```
963+
964+
With this signature, Dash's front-end will call
965+
`window.my_clientside_library.my_function` with the current
966+
values of the `value` properties of the components
967+
`my-input` and `another-input` whenever those values change.
968+
969+
Include a JavaScript file by including it your `assets/` folder.
970+
The file can be named anything but you'll need to assign the
971+
function's namespace to the `window`. For example, this file might
972+
look like:
973+
```
974+
window.my_clientside_library = {
975+
my_function: function(input_value_1, input_value_2) {
976+
return (
977+
parseFloat(input_value_1, 10) +
978+
parseFloat(input_value_2, 10)
979+
);
980+
}
981+
}
982+
```
983+
"""
984+
self._validate_callback(output, inputs, state)
985+
callback_id = _create_callback_id(output)
986+
987+
self.callback_map[callback_id] = {
988+
'inputs': [
989+
{'id': c.component_id, 'property': c.component_property}
990+
for c in inputs
991+
],
992+
'state': [
993+
{'id': c.component_id, 'property': c.component_property}
994+
for c in state
995+
],
996+
'clientside_function': {
997+
'namespace': clientside_function.namespace,
998+
'function_name': clientside_function.function_name
999+
}
1000+
}
1001+
9391002
# TODO - Update nomenclature.
9401003
# "Parents" and "Children" should refer to the DOM tree
9411004
# and not the dependency tree.
@@ -962,7 +1025,7 @@ def callback(self, output, inputs=[], state=[]):
9621025
'state': [
9631026
{'id': c.component_id, 'property': c.component_property}
9641027
for c in state
965-
]
1028+
],
9661029
}
9671030

9681031
def wrap_func(func):

dash/dependencies.py

+17-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
class DashDependency:
2+
# pylint: disable=too-few-public-methods
23
def __init__(self, component_id, component_property):
34
self.component_id = component_id
45
self.component_property = component_property
@@ -19,16 +20,26 @@ def __hash__(self):
1920
return hash(str(self))
2021

2122

22-
# pylint: disable=too-few-public-methods
23-
class Output(DashDependency):
23+
class Output(DashDependency): # pylint: disable=too-few-public-methods
2424
"""Output of a callback."""
2525

2626

27-
# pylint: disable=too-few-public-methods
28-
class Input(DashDependency):
27+
class Input(DashDependency): # pylint: disable=too-few-public-methods
2928
"""Input of callback trigger an update when it is updated."""
3029

3130

32-
# pylint: disable=too-few-public-methods
33-
class State(DashDependency):
31+
class State(DashDependency): # pylint: disable=too-few-public-methods
3432
"""Use the value of a state in a callback but don't trigger updates."""
33+
34+
35+
class ClientsideFunction:
36+
# pylint: disable=too-few-public-methods
37+
def __init__(self, namespace=None, function_name=None):
38+
self.namespace = namespace
39+
self.function_name = function_name
40+
41+
def __repr__(self):
42+
return 'ClientsideFunction({}, {})'.format(
43+
self.namespace,
44+
self.function_name
45+
)

0 commit comments

Comments
 (0)