Hi, I’ve built an AIO component that renders a Tooltip
and Popover
above a styled Div element. When a user hover’s on the styled Div, the Tooltip
will provide some arbitrary name above the Div; and when the Div is clicked on, a Popover
will show some other arbitrary info above the same Div element.
The AIO component works fine, however, I’m rendering the AIO component for every item within a list. This list is updated using a dcc.Interval every 5 seconds. So, I have encountered the issue of opening either a Tooltip
or Popover
above one of the rendered AIO components and having the opened Tooltip
or Popover
display for as long as the dcc.Interval has not updated the list of items that renders each of the AIO components. Ideally, the opened Tooltip
or Popover
would remain in the opened state until the user has closed the Tooltip
or Popover
.
To accomplish this UX, I’m attempting to update the disabled
state/property of the dcc.Interval to True
when the user clicks on one of the rendered AIO components from the list. By disabling the Interval, a Tooltip
or Popover
should continue being rendered above its associated Div element until the user chooses to close the Popover
or Tooltip
.
However, after implementing this logic via a callback, I encounter the following error:
Here’s the example of the AIO component which is called EndpointBlock
:
import uuid
import logging
import dash_bootstrap_components as dbc
from dash import dcc, Input, Output, State, html, callback, no_update, MATCH, dash_table
from influxdb import InfluxDBClient
import psycopg2
class EndpointBlockAIO(html.Div):
# A set of functions that create pattern-matching callbacks of the subcomponents
class ids:
block = lambda aio_id: {
'component': 'EndpointBlock',
'subcomponent': 'block',
'aio_id': aio_id
}
tooltip = lambda aio_id: {
'component': 'EndpointBlock',
'subcomponent': 'tooltip',
'aio_id': aio_id
}
popover = lambda aio_id: {
'component': 'EndpointBlock',
'subcomponent': 'popover',
'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, value, 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:
aio_id = str(uuid.uuid4())
# Define the component's layout
super().__init__([ # Equivalent to `html.Div([...])`
html.Div([],
id=self.ids.block(aio_id),
className="heatmap-block {0}".format('heatmap-red' if value['online_status'] == True else ''))
, dbc.Tooltip(
"{}".format(value["ip"]),
id=self.ids.tooltip(aio_id),
target=self.ids.block(aio_id),
is_open=False,
trigger=None,
placement="top")
, dbc.Popover(
"Test Popover",
id=self.ids.popover(aio_id),
target=self.ids.block(aio_id),
body=True,
trigger="click",
hide_arrow=True,
placement="top")
])
@callback(Output(ids.tooltip(MATCH), "is_open"),
Input(ids.block(MATCH), "n_clicks"),
State(ids.tooltip(MATCH), "is_open"))
def toggle_endpoint_tooltips(n_clicks, is_open):
if n_clicks:
return not is_open
else:
return is_open
Here’s how the AIO component is used:
def subnet_card(subnet, values):
blocks = []
# sort pattern prevents blocks from appearing in different order every reload
for index, value in enumerate(sorted(values, key=lambda endpoint: socket.inet_aton(endpoint["ip"])), start=1):
blocks.append(EndpointBlockAIO(value))
return html.Div([
html.Div([
subnet
], className="card-header"),
html.Div([
html.Div(blocks, className="subnet-heatmap", id="subnet-heatmap")
], className="card-body")
], className="card subnet-card m-2 p-0")
Here’s the callback that initiates the error mentioned above:
@callback(
Output('dashboard_task_interval', 'disabled'),
Input(EndpointBlockAIO.ids.block(MATCH), 'n_clicks'),
State(EndpointBlockAIO.ids.tooltip(MATCH), 'is_open')
)
def toggle_task_interval_for_tooltip(n_clicks, is_disabled):
if n_clicks:
return not is_disabled
else:
is_disabled
Does anyone understand this error, and are there any recommendations on how to fix this?