Skip to main content
Version: Next

13: DBC modal via signals

Extends Example 12 with an "About" NavLink that opens a DBC modal. The navbar emits a signal; the modal listens and opens. Shows how signals decouple components that don't share a parent. The navbar doesn't need a reference to the modal, only to the signal they both agree on.

The script

# -*- coding: utf-8 -*-

from dash import html, dcc
import dash_bootstrap_components as dbc
from dash_extensions.enrich import Output, Trigger
from weaverlet.base import WeaverletApp, WeaverletComponent, Identifier, SignalTrigger, SignalOutput
from weaverlet.components import SimpleRouterComponent, SignalComponent


class AboutModalComponent(WeaverletComponent):

modal_id = Identifier()
close_button_id = Identifier()

def __init__(self):
super().__init__()
self.open_modal_signal = SignalComponent()

def get_layout(self):
layout = \
html.Div(
[
self.open_modal_signal(),
dbc.Modal(
[
dbc.ModalHeader('About'),
dbc.ModalBody(children=[
dcc.Markdown('''
#### About

By Alberto Garcia-Robledo.
''')
]),
dbc.ModalFooter(
dbc.Button('Close', id=self.close_button_id,
className='ml-auto')
),
],
id=self.modal_id,
size="lg"
)
]
)

return layout

def register_callbacks(self, app):

@app.callback(
Output(self.modal_id, 'is_open'),
Trigger(self.close_button_id, 'n_clicks')
)
def close_modal():
return False

@app.callback(
Output(self.modal_id, 'is_open'),
SignalTrigger(self.open_modal_signal)
)
def open_modal():
return True


class PrimaryNavbarComponent(WeaverletComponent):

about_navlink_id = Identifier()

def __init__(self, **kwargs):
super().__init__(**kwargs)
self.about_modal_component = AboutModalComponent()

def get_layout(self, brand):
layout = \
dbc.NavbarSimple(
children=[
dbc.NavItem(dbc.NavLink('Page A', href='/a')),
dbc.NavItem(dbc.NavLink('Page B', href='/b')),
dbc.NavItem(dbc.NavLink(
'About', id=self.about_navlink_id, href='#')),
self.about_modal_component()
],
brand=brand,
color='primary',
dark=True
)

return layout

def register_callbacks(self, app):
@app.callback(
SignalOutput(self.about_modal_component.open_modal_signal),
Trigger(self.about_navlink_id, 'n_clicks')
)
def open_modal():
return {}


class MainPageComponent(WeaverletComponent):

def __init__(self, brand, page_content_component):
super().__init__()
self.primary_navbar = PrimaryNavbarComponent(name='primary_navbar')
self.page_content_component = page_content_component
self.brand = brand

def get_layout(self, pathname, hash, href, search):
layout = \
html.Div(
[
self.primary_navbar(brand=self.brand), # child component
dbc.Container(
[
self.page_content_component() # child component
],
fluid=True,
style={"padding": "0px", 'width': "100%"}
)
]
)

return layout


class ContentComponent(WeaverletComponent):

def __init__(self, body, **kwargs):
super().__init__(**kwargs)
self.body = body

def get_layout(self):
layout = \
dbc.Container([
dcc.Markdown(self.body)
])

return layout


class NotFoundPageComponent(WeaverletComponent):

def __init__(self, **kwargs):
super().__init__(**kwargs)

def get_layout(self, pathname):
layout = \
dcc.Markdown(f"# Page {pathname} not found!")

return layout


page_a_markdown = """
# Page A \

This is the content of Page A.
"""

page_b_markdown = """
# Page B \

This is the content of Page B.
"""

content_page_a = ContentComponent(body=page_a_markdown)
content_page_b = ContentComponent(body=page_b_markdown)
page_a_main_page = MainPageComponent(brand='Brand of Page A',
page_content_component=content_page_a)
page_b_main_page = MainPageComponent(brand='Brand of Page B',
page_content_component=content_page_b)
not_found_page = NotFoundPageComponent()

routes = {
'/': page_a_main_page,
'/a': page_a_main_page,
'/b': page_b_main_page
}
router = SimpleRouterComponent(
routes=routes,
not_found_page_component=not_found_page
)
wapp = WeaverletApp(root_component=router,
title='Simple Weaverlet + DBC app',
external_stylesheets=[dbc.themes.COSMO])

wapp.app.run()

Expected output

  • Two-page app with a navbar containing "Page A", "Page B", and an "About" link.
  • Clicking "About" opens a modal with the title "About" and the body "By Alberto Garcia-Robledo."
  • Clicking "Close" in the modal footer closes it.

Notes

  • The interesting wiring is the modal-open signal. AboutModalComponent owns a SignalComponent called open_modal_signal. The navbar's About-click callback emits {} to that signal; the modal's own callback listens via SignalTrigger and sets is_open=True.
  • The Close button uses a regular Trigger (not a signal trigger). The close logic is internal to the modal, no cross-component event needed.
  • Two callbacks both write to Output(self.modal_id, 'is_open'): one for open, one for close. This is allowed because WeaverletApp.app is a DashProxy with group= multi-output multiplexing. Stock Dash would reject this.
  • The navbar accesses the modal's signal via self.about_modal_component.open_modal_signal: only because the navbar owns the modal. If the modal lived elsewhere in the DAG, you'd pass the signal explicitly.

Try it locally

pip install "weaverlet[examples]"
python dbc_modal_app.py

See also