I need help to understand some fundamental workings of a Python Dash app.
Since I need to implement a quite complicated application as a Dash app, I came up with a basic interface for my components.
class IComponent:
def __init__(self, app:Dash, baseId:str) -> None:
self._app = app
self._id = baseId
self._component = None
def setup(self):
"""The calling scope will call this function to get the actual component to be added to the Dash layout."""
self._create()
self._registerCallbacks()
return self._component
def _create(self)
"""Implementation will create the Dash component and assign it to the self._component member."""
raise NotImplementedError
def _registerCallbacks(self):
"""Implementation will define all necessary callbacks for the component. Necessary component ids for interaction with other components may be passed to the implementation's constructor."""
raise NotImplementedError
An implementation for a very simple component, e.g. a modal asking the user for a text input, would look like this:
class TextInputModal(IComponent):
def __init__(self, app:Dash, baseId:str, o_storeText:str, t_openTrigger:str):
super().__init__(app, baseId)
self.o_storeText = o_storeText # this is the id of a Store component to which the user text should be stored
self.t_openTrigger = t_openTrigger # this is the id to a button that triggers the modal
def _create(self):
header = dbc.ModalHeader("Provide Input")
body = dbc.ModalBody(dbc.Input(id=self._id+"input"))
footer = dbc.ModalFooter(dbc.Button(id=self._id+"confirm", children="Confirm"))
modal = dbc.Modal(id=self._id, children=[header, body, footer], is_open=False)
self._component = modal
def _registerCallbacks(self):
app = self._app
id = self._id
@app.callback(
[Output(id, "is_open")],
[Input(self.t_openTrigger, "n_clicks"),
Input(id+"confirm", "n_clicks")],
[State(id, "is_open"],
prevent_initial_callback=True
)
def setVisibility(_, _, isOpen:bool):
return not IsOpen
@app.callback(
[Output(self.o_storeText, "data")],
[Input(id+"confirm", "n_clicks")],
[State(id+"input", "value"],
prevent_initial_callback=True
)
def setVisibility(text:str):
return text
The calling scope would instantiate the TextInputModal, pass the necessary arguments textInputModel = TextInputModal(...), especially the component ids it depends on and then add it to a Dash layout (or another component's children) by calling the textInputModal.setup() method.
This way allows for better structure of my modules, code separation, avoiding code duplication etc. For further improvement of this approach I need to know:
Can I use
IComponentclass members in my callbacks? (Other than I already successfully do to just assign ids and initialize callbacks!) I'm more thinking about e.g. putting some data into a class member, which is accessed by a callback to draw a plot upon a trigger. Or is this just a hidden violation of the "no globals" rule?How does the answer to (1.) change, if my Dash app is only used by ONE user locally (localhost)?
How can I think of the very fundamental flow in a Dash app (assume I am a total beginner)? Where are Outputs/Inputs created (server/localhost, browser?)? What part of a callback is server-side, which one is client-side? Which data is shared between clients etc.? Where do callbacks "live" (think of my member variable question...) etc.