Show and Tell - Dash Bootstrap Components

Hello,

I have extensively read over the multi-app program, and I finally understand it. I tested other apps and it works. I am however stuff at how to create a callback for connecting the different DropdownMenuItem instances I have to pull out a new page (i.e. i am confused with how to make inputs and outputs of the callback). I only need help with the index page. Here is my structure:

from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
import socket
from app import app
import page_1, page_2, page_3
import base64

image_filename = 'file/path.png'
encoded_image = base64.b64encode(open(image_filename, 'rb').read())

app.layout = html.Div([
    html.Img(
            id = 'image',
            alt='Image',
            src='data:image/png;base64,{}'.format(encoded_image.decode()),
            style= {
                     'width':'100%',
                     'height':'100px'
                     }
            ),
    dbc.NavbarSimple([
             dbc.DropdownMenu(
                     id = 'Menu',
                     nav=True,
                     in_navbar=True,
                     label="Menu",
                     children=[
                             dbc.DropdownMenuItem("Page 1",href='/page_1'),
                             dbc.DropdownMenuItem("Page 2",href='/page_2'),
                             dbc.DropdownMenuItem("Page 3",href='/page_3')
                             ]
                     )
             ]),
    html.Div(id='page-content')
])


@app.callback(Output('page-content', 'children'),
              [Input('Menu', 'href')])
def display_page(pathname):
    if pathname in ('/page_1','',None,'/'):
         return page_1.layout
    elif pathname == '/page_2':
         return page_2.layout
    elif pathname == '/page_3':
         return page_3.layout
    else:
        return '404'

host = socket.gethostbyname(socket.gethostname())
app.run_server(debug=False, host=host, port = 1002)

Hey @dayxx369

You need to add the dcc.Location component to your layout, then use it in a callback to monitor the pathname and render the layout accordingly. When you set href on each of the DropdownMenuItem components, they behave just like dcc.Link, so you don’t need to use either the DropdownMenu or DropdownMenuItem as inputs to any callbacks.

Something like this:

# add dcc.Location to your layout
app.layout = html.Div([
    dcc.Location(id="url"),
    ...
])

# use Location in the display_page callback
@app.callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    if pathname in ('/page_1','',None,'/'):
         return page_1.layout
    elif pathname == '/page_2':
         return page_2.layout
    elif pathname == '/page_3':
         return page_3.layout
    else:
        return '404'
2 Likes

@tcbegley
Great. I understand. Now, should I have the NavbarSimple in the index or should it be redefined in the other programs? I want to set the dropdown item to disabled if it’s currently on that page (i.e. other apps). Thanks again

really nice work can’t wait to test it

1 Like

@tcbegley
Nevermind, I answered my own question. The NavbarSimple should be redefined in the other programs (apps). I am really looking forward to how dash evolves!

@dayxx369
Correct, put the NavbarSimple in the index. The top level structure of the app should be something like

app.layout = html.Div([
    dcc.Location(...),
    dbc.NavbarSimple(...),
    html.Div(id="page-content"),
])

EDIT - You got in just before me, but I’ll leave my answer in case it helps someone else :slightly_smiling_face:

2 Likes

@dayxx369, sorry for the spam…

On the question of disabling links in the navbar depending on which page you’re on, you can write a callback that takes the location as an input, and sets the disabled prop of the relevant DropdownMenuItem accordingly. Though I would have thought that setting the active prop rather than disabled might be more natural?

Check out this example which does something similar with NavLink rather than DropdownMenuItem. I think you should be able to adapt it to your case pretty easily.

1 Like

Oh yes, I figured that out as well. thanks again!

Question. Can i still override css classes with my custom CSS file in the assets folder if i use your components??

Yes absolutely! All of the styles are applied through CSS anyway, so you can modify or add to them as you please.

dbc.themes.BOOTSTRAP is just a link to BootstrapCDN, if you prefer you can have a local copy of Bootstrap CSS, or a modified copy of it, or you can complement it with your own CSS.

2 Likes

thanks a lot it was my mistake i was messing with the wrong class

1 Like

Does anyone know how to use Sliders and Dropdown with dbc?

They seem to have been added here - https://github.com/facultyai/dash-bootstrap-components/pull/297 - but when I try to use dbc.Slider / dbc.Dropdown I get this:

Traceback (most recent call last):
  File "index.py", line 334, in <module>
    dbc.Slider(
AttributeError: module 'dash_bootstrap_components' has no attribute 'Slider'

My dbc version is up to date

Hey @Chris369,

dash-bootstrap-components doesn’t have it’s own versions of those components (basically because we wanted to minimise unnecessary duplication of functionality). The pull request you linked to is related to custom CSS that will restyle the Slider or Dropdown from dash-core-components to make it look more “bootstrapy”.

The use of this custom CSS hasn’t been documented yet, in the future we’d like to host our own Bootstrap stylesheets with some Dash specific features on a CDN and make it easy to access through dbc.themes or similar, but I haven’t got around to it.

In the meantime however, you can check out my dash-bootstrap-css repo which has compiled versions of the styles that were added in that pull request, ready for download.

To use them, just download one of the compiled stylesheets in dist/, put it in your app’s assets/ folder, and make sure that any component you want to apply the styles to is a child of a component with className="dash-bootstrap". For example I downloaded the minty stylesheet, added it to assets/ then ran this app

import dash
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash()

app.layout = dbc.Container(
    children=[
        dcc.Slider(min=0, max=10, step=1),
        dcc.Dropdown(options=[
            {"label": "Option 1", "value": 1},
            {"label": "Option 2", "value": 2},
        ]),
    ],
    className="dash-bootstrap p-5",
)

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

and with that code I see these “minty” version of the Slider and Dropdown.

image

Note that I no longer need to use external_stylesheets=[dbc.themes.MINTY] or anything like that, these are full Bootstrap stylesheets with a few extra features that can be used as a drop-in replacement for other Bootstrap stylesheets.

2 Likes

Thank you so much for the quick reply :slight_smile: And this looks perfect - just what I was after and looks easy enough to use - thank you!

1 Like

Great! The custom CSS still isn’t properly battle-tested, so if you do try it out and have any issues please let me know.

Hi @tcbegley. I would like to apply dash-bootstrap css to the modal component I am using, but not the other components of my app which I have styled manually.

For some reason when I add dash-bootstrap css file to my ./assets folder, the dash css is loading on my dash table, even though I have not declared the className=‘dash-bootstrap’ to to the div. Oddly, it is not impacting other divs in my app (just the modal which has the className, and the table Div for some odd reason.

Could you look at my code below? Any idea why this is happening?

from app import app
import dash
import dash_table
from dash_table.Format import Format, Scheme, Sign, Symbol
import dash_html_components as html
import dash_core_components as dcc
import pandas as pd
import python.extract_portfolio
from datetime import date as Date
from dash.dependencies import Input, Output, State
import dash_bootstrap_components as dbc
import dash_table.FormatTemplate as FormatTemplate

# load dataframe from python logic
df = python.extract_portfolio.trade_list.sort_index(ascending=False)

# reformat index / trade date
df['Trade_Date'] = pd.DatetimeIndex(df['Trade_Date']).strftime("%b, %d %Y")

# add external css (note: style tags override the external css)
#external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

# serve dash app
app3 = dash.Dash(
    __name__,
    server=app,
    routes_pathname_prefix='/dash3/',
)


# define app.layout
app3.layout = html.Div([
# add new transaction record
    html.Div(
    [
        dbc.Button("Open modal", id="open"),
        dbc.Modal(
            [
                dbc.ModalHeader("Header"),
                dbc.ModalBody("This is the content of the modal"),
                dbc.ModalFooter(
                    dbc.Button("Close", id="close", className="ml-auto")
                ),
            ],
            id="modal",
        ),
    ], className='dash-bootstrap'
),


# filter layouts

    html.Div([
        html.Td('Filter by Ticker(s)'),
        dcc.Dropdown(
            id='ticker-filter',
            options=[{'label': i, 'value': i} for i in df['Symbol_CUSIP'].unique()],
            placeholder='Select a ticker to filter by',
            multi=True,
            #value='AMZN'
        )
    ],
        style={'width': '20%', 'float': 'left', 'display': 'inline-block', 'fontFamily': 'Arial', 'fontSize': '12px'},
        #className='dash-bootstrap'
    ),
    html.Div([
        html.Td('Filter by Transaction Type(s)'),
        dcc.Dropdown(
            id='transaction-type-filter',
            options=[{'label': i, 'value': i} for i in df['Transaction_Type'].unique()],
            placeholder='Select a transaction type to filter by',
            multi=True,
        )
    ],
        style={'width': '20%', 'float': 'left', 'display': 'inline-block', 'fontFamily': 'Arial', 'fontSize': '12px'}
    ),
    html.Br(),
    html.Br(),
    html.Br(),





# table layout
    html.Div([
        html.H4(
            "Transaction Records",
            id='datatable-interactivity-container',
            style={'fontFamily': 'Arial'}
        ),
        dash_table.DataTable(
            id='table',
            page_action='none',

        # data import
            data=df.to_dict('rows'),
            columns=[
                {
                    'id': 'Trade_Date',
                    'name': 'Trade Date',
                },
                {
                    'id': 'Symbol_CUSIP',
                    'name': 'Ticker',
                },
                {
                    'id': 'Account',
                    'name': 'Account',
                },
                {
                    'id': 'Transaction_Type',
                    'name': 'Transaction Type',
                },
                {
                    'id': 'Description',
                    'name': 'Description',
                },
                {
                    'id': 'Quantity',
                    'name': 'Units',
                    'type': 'numeric',
                    'format': Format(group=',',precision=1,scheme=Scheme.fixed)
                },
                {
                    'id': 'Price',
                    'name': 'Price',
                    'type': 'numeric',
                    'format': Format(precision=2,scheme=Scheme.fixed,symbol='$', group=',')
                },
                {
                    'id': 'Amount',
                    'name': 'Transaction Value',
                    'type': 'numeric',
                    'format': Format(precision=2,group=',',scheme=Scheme.fixed,symbol='$')
                }
            ],
            style_table={
                'maxHeight': '100%',
                'width': '90%',
                'margin': '0 auto',
            },
            style_as_list_view=True,
            style_cell={
                'fontFamily': 'Arial',
                'textAlign': 'left',
                'height': '60px',
                'padding': '2px 22px',
                'whiteSpace': 'inherit',
                'overflow': 'hidden',
                'textOverflow': 'ellipsis',
                'font_size': '12px'
            },
            style_cell_conditional=[
                {
                    'if': {'column_id': 'Trade_Date'},
                    'textAlign': 'left',
                    'width': '150px'
                },
                {
                    'if': {'column_id': 'Account'},
                    'textAlign': 'left',
                    'width': '200px'
                }
            ],
            style_header={
                'fontWeight': 'bold',
                'backgroundColor': 'white',
            },
            style_data_conditional=[
                    {
                        'if': {
                            'filter_query': '{Transaction_Type} eq "Sale"',
                        },
                        'color': 'red',
                        'backgroundColor': 'rgb(248,216,212)'
                    },
                    {
                        'if': {
                            'filter_query': '{Transaction_Type} eq "Dividend"',
                        },
                        'backgroundColor': 'rgb(217,217,217)'
                    }
                ]

        ),
        html.Br(),
        html.Br(),
        html.Br()
    ],

    )
])
#print(df[df == value])


@app3.callback(
    Output('table', 'data'),
    [Input('ticker-filter', 'value'),
     Input('transaction-type-filter', 'value')])
def update_rows(selected_ticker, selected_transaction_type):
    if selected_ticker == [] or selected_ticker == None:
        if selected_transaction_type != []:
            df1 = df[df.Transaction_Type.isin(selected_transaction_type)]
            return df1.to_dict('rows')
        else:
            return df.to_dict('rows')
    if selected_ticker != []:
        if selected_transaction_type == [] or selected_transaction_type == None:
            df1 = df[df.Symbol_CUSIP.isin(selected_ticker)]
            return df1.to_dict('rows')
        else:
            df1 = df[df.Symbol_CUSIP.isin(selected_ticker)]
            df2 = df1[df1.Transaction_Type.isin(selected_transaction_type)]
            return df2.to_dict('rows')
@app3.callback(
    Output("modal", "is_open"),
    [Input("open", "n_clicks"), Input("close", "n_clicks")],
    [State("modal", "is_open")],
)
def toggle_modal(n1, n2, is_open):
    if n1 or n2:
        return not is_open
    return is_open

You can see in the picture below: The dash bootstrap css is working on the modal, but also impacting my table (using dash bootstrap fonts and stretching the columns). It is not the divs ‘Filter by Ticker’ or ‘Filter by Transaction Type’.

Hey @andrewmarco

The dash-bootstrap-css stylesheets are just the regular Bootstrap stylesheets + some additional styles that only get applied to descendants of an element with class dash-bootstrap, so all the regular Bootstrap styles get applied everywhere which is why your table is being affected.

If you only want to style the modal with Bootstrap, I would suggest copying just the modal styles from regular Bootstrap and putting them in a stylesheet inside your assets/ folder.

1 Like

Hi there, this is awesome, thank you for sharing!
I would like to add an svg icon inside a dbc.InputGroupAddon. Something along the lines of:

dbc.InputGroupAddon([
        "Add-on text",
        html.Img(src=app.get_asset_url('icons/info_circle.svg'))
    ], 
    addon_type="append"
)

It works, but it does not apply the background styling of the addon. See image:
image

I got the icon svg from:


saved it into info_circle.svg and removed this part class="bi bi-info-circle" fill="currentColor" from the definition.

What am I doing wrong? All help is welcome!

Hey @jbebic, looks like there’s something a bit funny going on with the InputGroupAddon component. You can work around it by wrapping the text and image in a span and adding the relevant class name yourself

dbc.InputGroupAddon(
    html.Span(
        [
            "Add-on text",
            html.Img(
                src=app.get_asset_url("icons/info_circle.svg"),
                className="ml-2",  # bootstrap utility class, sets left margin
            ),
        ],
        className="input-group-text",
    ),
    addon_type="append",
)

Looks like this for me

image

1 Like

Thank you for the fast and complete response, really appreciate it!