Urizos
Urizos

Reputation: 51

Is there a way to add the "select all" option in the multiselect widget of python's streamlit package?

I’m developing a streamlit app which contains a multiselect widget with 10 options. Is there an alternative to add a button that selects all the options to avoid selecting them one by one?

I tried adding a previous button to the multiselect, but using another widget resets the selection. Here is an example code:

import streamlit as st

if st.sidebar.button('Add all years'):
    year_filter = st.sidebar.multiselect('Select a year',[2000,2001,2002,2003,2004], default=[2000,2001,2002,2003,2004])
else:
    year_filter = st.sidebar.multiselect('Select a year',[2000,2001,2002,2003,2004], default=[2000])

type_filter = st.sidebar.multiselect('Choose a type',['Control','Experimental'], default=['Experimental'])

st.write(year_filter, type_filter)

Upvotes: 5

Views: 4843

Answers (1)

vvvvv
vvvvv

Reputation: 31640

There is a way using the state:

import streamlit as st
from streamlit.ReportThread import get_report_ctx
from streamlit.hashing import _CodeHasher
from streamlit.server.Server import Server


class _SessionState:
    def __init__(self, session, hash_funcs):
        """Initialize SessionState instance."""
        self.__dict__["_state"] = {
            "data": {},
            "hash": None,
            "hasher": _CodeHasher(hash_funcs),
            "is_rerun": False,
            "session": session,
        }

    def __call__(self, **kwargs):
        """Initialize state data once."""
        for item, value in kwargs.items():
            if item not in self._state["data"]:
                self._state["data"][item] = value

    def __getitem__(self, item):
        """Return a saved state value, None if item is undefined."""
        return self._state["data"].get(item, None)
        
    def __getattr__(self, item):
        """Return a saved state value, None if item is undefined."""
        return self._state["data"].get(item, None)

    def __setitem__(self, item, value):
        """Set state value."""
        self._state["data"][item] = value

    def __setattr__(self, item, value):
        """Set state value."""
        self._state["data"][item] = value
    
    def clear(self):
        """Clear session state and request a rerun."""
        self._state["data"].clear()
        self._state["session"].request_rerun()
    
    def sync(self):
        """Rerun the app with all state values up to date from the beginning to fix rollbacks."""

        # Ensure to rerun only once to avoid infinite loops
        # caused by a constantly changing state value at each run.
        #
        # Example: state.value += 1
        if self._state["is_rerun"]:
            self._state["is_rerun"] = False
        
        elif self._state["hash"] is not None:
            if self._state["hash"] != self._state["hasher"].to_bytes(self._state["data"], None):
                self._state["is_rerun"] = True
                self._state["session"].request_rerun()

        self._state["hash"] = self._state["hasher"].to_bytes(self._state["data"], None)


def _get_session():
    session_id = get_report_ctx().session_id
    session_info = Server.get_current()._get_session_info(session_id)

    if session_info is None:
        raise RuntimeError("Couldn't get your Streamlit Session object.")
    
    return session_info.session


def _get_state(hash_funcs=None):
    session = _get_session()

    if not hasattr(session, "_custom_session_state"):
        session._custom_session_state = _SessionState(session, hash_funcs)

    return session._custom_session_state


def main():
    state = _get_state()

    if state.previous_years_selected is None:
        state.previous_years_selected = [2000]
    
    if st.sidebar.button('Add all years'):
        year_filter = st.sidebar.multiselect('Select a year', [2000,2001,2002,2003,2004], default=[2000,2001,2002,2003,2004])
    else:
        year_filter = st.sidebar.multiselect('Select a year', [2000,2001,2002,2003,2004], default=state.previous_years_selected)

    # The next default (in case no one clicks on "Add all years")
    # will be the current years selected
    state.previous_years_selected = year_filter

    state.sync()


if __name__ == "__main__":
    main()

The _StateSession object keeps data across Streamlit's reloads, meaning that if you interact a widget, the page reloads but the state keeps the information.
Here, we keep the information about the years that were selected during the last interaction with the "year selector" and provide it as a default for next reload (default=state.previous_years_selected).

You can add whatever data you want to the state object. Just make sure it does exist before modifying it:

if state.previous_years_selected is None:
    state.previous_years_selected = [2000]

Upvotes: 3

Related Questions