AIO Component properties (for Input/Output)

I’ve been reading the documentation and the examples about the All-In-One components, but there is still a question I have that I cannot seem to answer…

Is it possible to define properties for the component itself (rather than its subcomponents), in such a way that the can be used as Input or Output in app-level callbacks?

An example: Imagine an AIO component that would have a disabled property, allowing other parts of the UI to enable/disable it (at which point an AIO-internal callback would then disable all its internal sub-components).
And vice-versa: some internal logic to the component could put it in an “alert” state, that the app itself could be listening to and react to.

1 Like

Hi @Wabiloo,

Yes, this should be possible. The AIO component is a collection of sub components, all of which can be used in callbacks. You could designate one of the components to store the enable/disable state. . This component can be updated by other parts of the UI, and the component can have internal callbacks triggered by a change in any of it’s props in a callback.

This theme switch components isn’t exactly what you are looking for but it might be helpful to see how they work. Then this example demos how the theme switch component can be used to trigger a callback – it updates the figure template making the figure match the new theme colors.

1 Like

Ok, but then you seem to confirm what I was saying. I can’t define new properties attached to the AIO itself, I have to rely on the sub-components.

The “disabled” use case would be easy, since most sub-components have their own disabled property.

But what about the “alert” one? I’d need some sort of hidden Div to store/retrieve that state?
That would lead me to having to write something like:


@app.callback(
    Output('alert-info', 'children'),
    Input(CardWithAlertAIO.ids.alert('my-card'), 'value')
)
def display_alert(value):
    return f'An alert was raised'

Something like that?

Yes, but I would recommend using a dcc.Store component rather than a hidden Div.

1 Like

Ah yes, that makes more sense.

Let me throw another question. Something I’ve not found a way to solve yet (mentally).

What I’m trying to build is a component that is a “wrapper” to fit in a dashboard. Let’s call it a “card”
It have wrapper-level information (such as that “disabled” toggle and “alert state”), but the content of it will be user-defined outside of the AIO itself.

For example, it would look something like this:
Screenshot 2022-07-18 at 20.29.22

The overall look, the title, the toggle switch, are all in the card AIO.
But the slider inside (and whatever other HTML structure) is provided as a parameter to the AIO.

What I’d like however is to define communication within the card:

  • A way to disable the slider (or whatever dcc or daq component is in the card) when the card itself is disabled (from outside the card)
  • A way to be told when the slider (or whatever dcc or daq component is in the card) has an “alert”, so that the card can display it (and set itself on alert, for callbacks outside the card).

I can imagine how with pattern-matching callback I could do that, but I can’t think of an elegant way to “tie-in” the card (and its id) to its internal components…

Hmm, I’m not sure if you can add arbitrary components to an AIO. Maybe you could pass a component to the children prop of an html.Div, but I haven’t tried that. But if you find a way, I hope you post your solution.

I’ve been trying many things by now, including passing components to the AIO and dynamically “populating” those components with dictionary ids (to “embed” them in the AIO instance), but I’m generally getting nowhere with it. Callbacks are not firing as I expect them to, and therefore nothing really works.
I was under the impression that when specifying a dictionary id on an Input or Output, I could leave some elements out (as per Pattern-Matching Callbacks | Dash for Python Documentation | Plotly), but it seems that’s not the case, and the matching will not succeed if I don’t have every property specified with an ALL

Here’s an example, in case someone can spot an obvious mistake.
The idea is that:

  • There is a ControlCardAIO with a toggle switch
  • which can take any children

On interactions:

    1. Any sliders in the children should get disabled if the card is disabled
    1. I can determine (from outside the AIO) if it has been disabled, and what the value of the slider(s) are

Here’s my test code:


from dash import dash, dcc, html, MATCH, ALL, callback
from dash.dependencies import Input, Output

import dash_daq as daq
import uuid


app = dash.Dash(
    __name__,
    meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}],
)
app.title = "Testing ControlCardAIO"
server = app.server
app.config["suppress_callback_exceptions"] = False

APP_PATH = str(pathlib.Path(__file__).parent.resolve())

control_color = "#FEC037"


class ControlCardAIO(html.Div):

    # A set of functions that create pattern-matching callbacks of the subcomponents
    class ids:
        toggle = lambda aio_id: {
            'component': 'ControlCardAIO',
            'subcomponent': 'toggle',
            'aio_id': aio_id
        }

    # Make the ids class a public class
    ids = ids

    # Define the arguments of the All-in-One component
    def __init__(
            self,
            disabled=False,
            content=None,
            aio_id=None
    ):
        # Allow developers to pass in their own `aio_id` if they're
        # binding their own callback to a particular component.
        if aio_id is None:
            # Otherwise use a uuid that has virtually no chance of collision.
            # Uuids are safe in dash deployments with processes
            # because this component's callbacks
            # use a stateless pattern-matching callback:
            # The actual ID does not matter as long as its unique and matches
            # the PMC `MATCH` pattern..
            aio_id = str(uuid.uuid4())

        # Alter the sub-component identifiers
        enrich_id(content, aio_id)

        # Define the component's layout
        super().__init__([  # Equivalent to `html.Div([...])`
            html.Div(
                children=[
                    # main switch, should disable the controls within the card
                    html.Div(daq.BooleanSwitch(on=True, id=self.ids.toggle(aio_id))),
                    html.Br(),
                    html.Div(children=content)
                ],
                style=dict(border='1px solid blue', width='300px')
            )
        ])

    # Define this component's stateless pattern-matching callback
    # that will apply to every instance of this component.

    # Disable any slider if card's main toggle is off
    @callback(
        # will not work if `'id': ALL` is not added
        Output({'aio_id': MATCH, 'component_type': 'Slider', 'id': ALL}, 'disabled'),
        Input(ids.toggle(MATCH), 'on')
    )
    def toggle_slider_state(toggle_state):
        return [not toggle_state]


def enrich_id(element, aio_id):
    if hasattr(element, 'id'):
        setattr(element, 'id', {
            'id': getattr(element, 'id'),
            'aio_id': aio_id,
            'component_type': element.__class__.__name__
        })

    if isinstance(element, list):
        for el in element:
            enrich_id(el, aio_id)

    if hasattr(element, 'children') and isinstance(getattr(element, 'children'), list):
        enrich_id(getattr(element, 'children'), aio_id)

app.layout = html.Div([
    ControlCardAIO(aio_id='card1',
                   content=html.Div(
                       children=[
                           html.P("ID = card1"),
                           dcc.Slider(min=0, max=10, value=5, id='slider1'),
                           # dcc.Checklist(options=['normal', 'elevated', 'critical'], id='check1')
                       ])
                   ),

    html.P([
        "- Is Card1 disabled? ",
        html.Span("n/a", id='control1')
    ]),
    html.P([
        "- The value of the slider in Card1 is: ",
        html.Span("n/a", id='control2')
    ])

])


@app.callback(
    Output('control1', 'children'),
    # Not adding `'component': ALL` prevents pattern matching
    Input({'aio_id': 'card1', 'subcomponent': 'toggle', 'component': ALL}, 'on')
)
def react_to_toggle(state):
    return [str(state)]


@app.callback(
    Output('control2', 'children'),
    # Not adding `'component_type': ALL` prevents pattern matching
    Input({'aio_id': 'card1', 'id': 'slider1', 'component_type': ALL}, 'value'),
)
def react_to_inner_component(value2):
    return [str(value2)]


if __name__ == "__main__":
    app.run_server(debug=True, port=8051)

Observations:

    1. only works if I add the ‘id’ component in the callback inside the AIO (see inline comments), even though it may or may not actually exist
    1. only works if I provide a “complete” complex id to the app-level callbacks’ Inputs, stating all components that may have been set (see inline comments)

In both cases, I seem to have to over-qualify the composite identifiers with things I don’t want to have to define. In doing so, I also force the input to be an array of values, instead of a single one, which makes the logic unnecessarily complex…

I am a bit surprised by having to do that. In the documentation on pattern-matching there is this line:

  • In fact, in this example, we didn’t actually need 'type': 'filter-dropdown'. The same callback would have worked with Input({'index': ALL}, 'value'). We included 'type': 'filter-dropdown' as an extra specifier in case you create multiple sets of dynamic components.

Did I misunderstand what that means? I took that as meaning "you don’t need to provide all specifiers in a composite ID, if some of the specifier’s values are not relevant in the determination of the components used in the callback…