St.footer()

Hello Fallas,

since I was amazed that my footer looks so neat,
I have been thinking about how to make it more general, so that others may benefit from it.
Also i wanted to make it reusable and easy to configure.
Therefore I have come up with the following code (python > 3.7):

import streamlit as st
from htbuilder import HtmlElement, div, ul, li, br, hr, a, p, img, styles, classes, fonts
from htbuilder.units import percent, px
from htbuilder.funcs import rgba, rgb


def image(src_as_string, **style):
    return img(src=src_as_string, style=styles(**style))


def link(link, text, **style):
    return a(_href=link, _target="_blank", style=styles(**style))(text)


def layout(*args):

    style = """
    <style>
      # MainMenu {visibility: hidden;}
      footer {visibility: hidden;}
     .stApp { bottom: 105px; }
    </style>
    """

    style_div = styles(
        position="fixed",
        left=0,
        bottom=0,
        margin=px(0, 0, 0, 0),
        width=percent(100),
        color="black",
        text_align="center",
        height="auto",
        opacity=1
    )

    style_hr = styles(
        display="block",
        margin=px(8, 8, "auto", "auto"),
        border_style="inset",
        border_width=px(2)
    )

    body = p()
    foot = div(
        style=style_div
    )(
        hr(
            style=style_hr
        ),
        body
    )

    st.markdown(style, unsafe_allow_html=True)

    for arg in args:
        if isinstance(arg, str):
            body(arg)

        elif isinstance(arg, HtmlElement):
            body(arg)

    st.markdown(str(foot), unsafe_allow_html=True)


def footer():
    myargs = [
        "Made in ",
        image('https://avatars3.githubusercontent.com/u/45109972?s=400&v=4',
              width=px(25), height=px(25)),
        " with ❤️ by ",
        link("https://twitter.com/ChristianKlose3", "@ChristianKlose3"),
        br(),
        link("https://buymeacoffee.com/chrischross", image('https://i.imgur.com/thJhzOO.png')),
    ]
    layout(*myargs)


if __name__ == "__main__":
    footer()

You can easily adjust the footer to your needs. You just have to pass the args in the appropriate order. Currently there are four types of args possible:

  1. Text simply with " "
  2. Images with source, styles
  3. links with href, text and styles
  4. HtmlElement (e.g. br() for line break)

In addition (without guarantee) all four components can be nested arbitrarily.
For the general layout of the footer you can adjust style_div and stlye_p respectively.

This is how it looks like:

Ofc, it is not perfect, but I guess a good start.
For all those who want it, feel free to reuse it and improve it.
Also it would be nice, if you guys @streamlit could include it in the library
so that I can use it easier next time too :grin:. Maybe st.footer() would be nice?

Cheers
Chris

16 Likes

Nice! We’ve been considering adding something like that to Streamlit. Will bookmark this for when we have another discussion on this topic

It’s also cool that you’re using my htbuilder library :smiley:

6 Likes

Great job on the footer Chris! :slight_smile:

2 Likes

:slight_smile: It is only possible due to the fact that i am

Standing on the shoulders of giants

htbuilder is a great library, once I understood how it works.
What i did not see was general style Class for the document so I had to do it with html.

<style>
  # MainMenu {visibility: hidden;}
  footer {visibility: hidden;}
</style>

or is there @thiago ?

Main props to @Charly_Wargnier for the idea of the footer!

2 Likes

Hey @chris_klose, I tried executing the code but stuck at this error.

ImportError: cannot import name ‘div’

Traceback:

File "/usr/local/lib/python3.6/dist-packages/streamlit/script_runner.py", line 324, in _run_script
    exec(code, module.__dict__)File "/content/app.py", line 2, in <module>
    from htbuilder import HtmlElement, div, ul, li, br, hr, a, p, img, styles, classes, fonts

did you install htbuilder ?
pip install htbuilder

yes, I did install pip install htbuilder

Error:


ImportError Traceback (most recent call last)
in ()
----> 1 from htbuilder import div

ImportError: cannot import name ‘div’


Not currently.

Although it should be easy to add that to htbuilder, with an API like:

style({
  '.foo > bar': styles(color='red', visibility='hidden'),
  '#baz': styles(position='absolute', top=px(10)),
})

If I have some time later this week I’ll look into this!

1 Like

You should be able to replace p()() with just p()

1 Like

Would you please provide a SS of how this footer would look like ?

Ofc, here yo go.

You can easily adjust the footer to your needs. You just have to pass the args in the appropriate order. Currently there are four types of args possible:

  1. Text simply with " "
  2. Images with source, styles
  3. links with href, text and styles
  4. HtmlElement (e.g. br() for line break)

In addition (without guarantee) all four components can be nested arbitrarily.
For the general layout of the footer you can adjust style_div and stlye_p respectively.

myargs = [
    "Made in ",
    image('https://avatars3.githubusercontent.com/u/45109972?s=400&v=4',
          width=px(25), height=px(25)),
    " with ❤️ by ",
    link("https://twitter.com/ChristianKlose3", "@ChristianKlose3"),
    br(),
    link("https://buymeacoffee.com/chrischross", image('https://i.imgur.com/thJhzOO.png')),
]
2 Likes

I am looking forward to get a streamlit.footer() function.

3 Likes

I wish to keep the style and position of the default footer ‘Made with Streamlit’. But I like to replace the footer text with something that you showed:

How can I do that?

@rafisics Check out Streamlit footer

1 Like

Thanks, @Heflin_Stephen_Raj_S
I will check it.

Hi there, I collaborate with my code:

def image(src_as_string, **style):
    return img(src=src_as_string, style=styles(**style))

def link(link, text, **style):
    return a(_href=link, _target="_blank", style=styles(**style))(text)

def layout(*args):

    style = """
    <style>
      # MainMenu {visibility: hidden;}
      footer {visibility: hidden;}
    </style>
    """

    style_div = styles(
        left=0,
        bottom=0,
        margin=px(0, 0, 0, 0),
        width=percent(100),
        text_align="center",
        height="60px",
        opacity=0.6
    )

    style_hr = styles(
    )

    body = p()
    foot = div(style=style_div)(hr(style=style_hr), body)

    st.markdown(style, unsafe_allow_html=True)

    for arg in args:
        if isinstance(arg, str):
            body(arg)
        elif isinstance(arg, HtmlElement):
            body(arg)

    st.markdown(str(foot), unsafe_allow_html=True)

def footer():
    myargs = [
        "<b>Made with</b>: Python 3.8 ",
        link("https://www.python.org/", image('https://i.imgur.com/ml09ccU.png',
        	width=px(18), height=px(18), margin= "0em")),
        ", Streamlit ",
        link("https://streamlit.io/", image('https://docs.streamlit.io/en/stable/_static/favicon.png',
        	width=px(24), height=px(25), margin= "0em")),
        ", Docker ",
        link("https://www.docker.com/", image('https://www.docker.com/sites/default/files/d8/styles/role_icon/public/2019-07/Moby-logo.png?itok=sYH_JEaJ',
              width=px(20), height=px(18), margin= "0em")),
        " and Google APP Engine ",
        link("https://cloud.google.com/appengine", image('https://lh3.ggpht.com/_uP6bUdDOWGS6ICpMH7dBAy5LllYc_bBjjXI730L3FQ64uS1q4WltHnse7rgpKiInog2LYM1',
              width=px(19), height=px(19), margin= "0em", align="top")),
        br(),
    ]
    layout(*myargs)

if __name__ == "__main__":
    footer()

2 Likes

Hello Chris,

Thanks for the great work, I can also use the free style footer to add the registration number of my domain name (maybe necessary for legitimate domain use in China) and salute to Streamlit.
However, I found that the footer does not switch its font color following the theme of the main page. Maybe this is because st.markdown(arg, unsafe_allow_html=True) uses the way to embed the iframe to display the content.
So, as a novice in JavaScript, I used a little bit of JavaScript in the footer to monitor the class name change of the stApp div. When it changes, it will convert the color of the text to easy to use according to the RGB to gray value psychological formula in my code. A clear color (black or white).
I hope it helps someone using st.footer. And, thanks for the really cool htbuilder!

import streamlit as st
from streamlit.components.v1 import html
from htbuilder import HtmlElement, div, ul, li, br, hr, a, p, img, styles, classes, fonts
from htbuilder.units import percent, px
from htbuilder.funcs import rgba, rgb


def image(src_as_string, **style):
    return img(src=src_as_string, style=styles(**style))


def link(link, text, **style):
    return a(_href=link, _target="_blank", style=styles(**style))(text)


def layout(*args):
    style = """
    <style>
      # MainMenu {visibility: hidden;}
      footer {visibility: hidden;}
     .stApp { bottom: 80px; }
    </style>
    """

    style_div = styles(
        position="fixed",
        left=0,
        bottom=0,
        margin=px(0, 0, 0, 0),
        width=percent(100),
        color="black",
        text_align="center",
        height="auto",
        opacity=1
    )

    # 分割线
    style_hr = styles(
        display="block",
        margin=px(0, 0, 0, 0),
        border_style="inset",
        border_width=px(2)
    )

    # 修改p标签内文字的style
    body = p(
        id='myFooter',
        style=styles(
            margin=px(0, 0, 0, 0),
            # 通过调整padding自行调整上下边距以达到满意效果
            padding=px(5),
            # 调整字体大小
            font_size="0.8rem",
            color="rgb(51,51,51)"
        )
    )
    foot = div(
        style=style_div
    )(
        hr(
            style=style_hr
        ),
        body
    )

    st.markdown(style, unsafe_allow_html=True)

    for arg in args:
        if isinstance(arg, str):
            body(arg)

        elif isinstance(arg, HtmlElement):
            body(arg)

    st.markdown(str(foot), unsafe_allow_html=True)

    # js获取背景色 由于st.markdown的html实际上存在于iframe, 所以js检索的时候需要window.parent跳出到父页面
    # 使用getComputedStyle获取所有stApp的所有样式,从中选择bgcolor
    js_code = '''
    <script>
    function rgbReverse(rgb){
        var r = rgb[0]*0.299;
        var g = rgb[1]*0.587;
        var b = rgb[2]*0.114;
        
        if ((r + g + b)/255 > 0.5){
            return "rgb(49, 51, 63)"
        }else{
            return "rgb(250, 250, 250)"
        }
        
    };
    var stApp_css = window.parent.document.querySelector("#root > div:nth-child(1) > div > div > div");
    window.onload = function () {
        var mutationObserver = new MutationObserver(function(mutations) {
                mutations.forEach(function(mutation) {
                    /************************当DOM元素发送改变时执行的函数体***********************/
                    var bgColor = window.getComputedStyle(stApp_css).backgroundColor.replace("rgb(", "").replace(")", "").split(", ");
                    var fontColor = rgbReverse(bgColor);
                    var pTag = window.parent.document.getElementById("myFooter");
                    pTag.style.color = fontColor;
                    /*********************函数体结束*****************************/
                });
            });
            
            /**Element**/
            mutationObserver.observe(stApp_css, {
                attributes: true,
                characterData: true,
                childList: true,
                subtree: true,
                attributeOldValue: true,
                characterDataOldValue: true
            });
    }
    

    </script>
    '''
    html(js_code)


def footer():
    # use relative path to show my png instead of url
    ######
    with open('footer_st_logo.png', 'rb') as f:
        img_logo = f.read()
    logo_cache = st.image(img_logo)
    logo_cache.empty()
    ######
    myargs = [
        "沪ICP备2077777777号-1	Made in ",
        image('media/ccb3c3657680f3d36265f4183c1cedde710d7242e401ab56e34bd1eb.png',
              width=px(25), height=px(25)),
        " with ❤️ by ",
        link("https://www.zhihu.com/people/Gu.meng.old-dream", "@wangnov"),
    ]
    layout(*myargs)


if __name__ == "__main__":
    footer()

Like and encourage WeChat group friends :smiling_face_with_three_hearts:

1 Like

Hi @Wangnov,

that is a nice improvement.
I like it and thanks for your efforts to make the footer more appealing!

Best regards
Chris

1 Like