Skip to main content

Streamlit component that listens for postMessage events and reports back

Project description

streamlit-notification_center-component

Streamlit component that listens to postMessage() traffic with a certain format.

Installation instructions

pip install streamlit_notification_center_component

Usage instructions

import streamlit as st
from streamlit_notification_center_component import notification_center

notification_center(key="globalnc")

Helper JavaScript methods

function ncSendMessage(target, body) {
  Array.from(window.parent.frames).forEach((frame) => {
    frame.postMessage({ type: 'nc', target: target, payload: body }, "*")
  })
}

function ncClearTarget(target) {
  Array.from(window.parent.frames).forEach((frame) => {
    frame.postMessage({ type: 'nc', target: target, command: 'clear' }, "*")
  })
}

function ncClearAll() {
  Array.from(window.parent.frames).forEach((frame) => {
    frame.postMessage({ type: 'nc', command: 'clear-all' }, "*")
  })
}

Let's talk about what's going on here. Each of these three functions loop over all frames in the parent and invoke a postMessage on them. The type: 'nc' code MUST be present or the notification_center widget will ignore the message.

In cases where clearing the data from state on the streamlit side is desired, a command: property needs to be included. There are two possible values:

  • 'clear': when present, a target: property must also be included to identify what to clear
  • 'clear-all': when present, no other value than type: 'nc' needs be present. All messages stored in the st.session_state._nc_data dict will be removed.

Lastly, when it comes to a normal message to be queued, the way this works is that streamlit will receive the value from the component's call to Streamlit.setComponentValue(). This will be a pre-vetted object conforming to the following TypeScript type:

export type NotificationMessage = {
 type: 'nc';
 target?: string;
 payload?: any;
 from?: string;
 command?: null | 'clear' | 'clear-all';
}

When not sending a command to clear and type, target and payload are all present, then the dict st.session_state._nc_data will have its key .setdefault(target, []). This ensures that there is at least an empty array of values for the supplied target. Then the payload object is appended to this array. All streamlit code can check for and use values.

Helper Python Methods

These streamlit python functions are also defined

from streamlit_notification_center_component import nc_get, nc_get_last, nc_get_all, nc_set, nc_has, nc_clear, nc_clear_all

def nc_get(key, default_value=None):
  """
  Retrieves the value associated with a given key from the session state.
  
  Args:
      key (str): The key for the value to retrieve.
      default_value (optional): The value to return if the key is not found. Defaults to None.

  Returns:
      The value associated with the key if found, or the default value if the key is not found.
  """
  if '_nc_data' not in st.session_state:
    st.session_state._nc_data = {}

  if st.session_state._nc_data.get(key) != None:
    return st.session_state._nc_data[key]
  else:
    return default_value
    
def nc_get_last(key, default_value=None):
  """
  Retrieves the value associated with a given key from the session state.
  
  Args:
      key (str): The key representing an array of messages with tis target
      default_value (optional): The value to return if the key is not found. Defaults to None.

  Returns:
      The last value for the specified `key` rather than the whole array. If no
      value is forthcoming, `default_value` is returned instead.
  """
  if '_nc_data' not in st.session_state:
    st.session_state._nc_data = {}

  if st.session_state._nc_data.get(key) != None:
    channel = st.session_state._nc_data.get(key)
    if channel:
      return channel[-1]
    else:
      return default_value 
  else:
    return default_value

def nc_get_all():
  """
  Retrieves all key-value pairs stored in the session state.
  
  Returns:
      dict: A dictionary containing all key-value pairs in the session state.
  """
  return st.session_state._nc_data

def nc_set(key, value):
  """
  Stores a value in the session state, associated with a given key.
  
  Args:
      key (str): The key to associate with the value.
      value: The value to store.
  """
  channel = st.session_state.setdefault('_nc_data', {}).setdefault(key, [])
  channel.append(value)

def nc_has(key):
  """
  Checks whether a given key exists in the session state.
  
  Args:
      key (str): The key to check for existence.

  Returns:
      bool: True if the key exists, False otherwise.
  """  
  return [False, True][nc_get(key) != None]

def nc_clear(key):
  """
  Clears the value associated with a given key in the session state.
  
  Args:
      key (str): The key for the value to clear.
  """  
  nc_get(key, []).clear()

def nc_clear_all():
  """
  Clears all key-value pairs stored in the session state.
  """
  st.session_state._nc_data = {}

Usage Examples

Okay, down to brass tacks. The streamlit_notification_center_component's primary purpose is to allow code in other custom components to send data back to streamlit in one shared location or to allow HTML in a call to st.components.v1.html to communicate back to the Python streamlit side of things. Currently layout is very limited in streamlit from an HTML perspective. Being able to write a quick layout using something like a table or set of inline-block divs is doable using the st.components.v1.html approach. However as soon as you have an action or other feature that needs to send data back to streamlit, you've moved into custom component territory.

Working with st.components.v1.html

Let's take the most common example first. If you wanted to introduce the following HTML with a stylized link that looks like a button

notification_center(key="globalnc")
st.components.v1.html(
  f'''
    <a class="stylized-button" onclick="sendData()">Button</a>
    <style>
      a.stylized-button {{
        padding: 0.25em;
        margin: 0.1em;
        border: 1px solid slategrey;
        background-color: lightslategray;
        border-radius: 5px;
        color: lightyellow;
        cursor: pointer;
        display: inline-block;
        user-select: none;        
      }}
    </style>
    <script>
      function sendData() {{
        ncSendMessage('stylized-button', {{msg: 'Stylized Button Pressed'}})
      }}

      function ncSendMessage(target, body) {{
        Array.from(window.parent.frames).forEach((frame) => {{
          frame.postMessage({{ type: 'nc', target: target, payload: body }}, "*")
        }})
      }}      
    </script>
  '''
)

What's going on here? Well, first off we are creating a new instance of the notification_center() component. The key supplied can be anything but will positively identify this NotificationCenter instance from another should there be one. If debug printing is enabled (see below) then you'll see "[NotificationCenter(key)][target] value" strings in the python logs. Note that key will be the key= value supplied when the notification_center() component is created, target will be the key the array of messages will be appended to and value will be the array of values for that target.

Then we are injecting into the page a block of HTML with some CSS and JavaScript. You'll note in this example that the ncSendMessage function from above has been included in the block. This is the easiest way to handle sending a postMessage() call to all frames in the parent of the iframe this code will reside in. The NotificationCenter component will be residing in another iframe with how streamlit works.

We now have the ability to inject custom HTML that can speak back to streamlit into your streamlit pages. Something that was not provided by default behavior.

Working with custom components

Other components should be able to import notification_center and use the nc- prefixed python functions for communicating with NotificationCenter data. As components send messages to the NotificationCenter, those values can be checked on the python side and can be worked with.

Debug messaging

If you're running into trouble, turn on debug messaging. This is a runtime variable that is set in

st.session_state.setdefault('_nc_debug', False)

To turn it on, simply change the value to True

st.session_state._nc_debug = True

Visual examples

Screenshot 2023-08-16 at 2 25 06 PM

After this point you'll see NotificationCenter messages appearing in your Python console.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

streamlit_notification_center_component-0.0.3.tar.gz (451.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file streamlit_notification_center_component-0.0.3.tar.gz.

File metadata

File hashes

Hashes for streamlit_notification_center_component-0.0.3.tar.gz
Algorithm Hash digest
SHA256 7a0f22a7bbb84763d3fed1b9a615f3acac84cf14e2f034d02fefb056fb3cfcda
MD5 bc2d937046312ee47e4dca3dacb8a7de
BLAKE2b-256 7790fdf30daf7c1e36dbfa0db8e73c369d63a98f2adfa3df903be624ed48f7ac

See more details on using hashes here.

File details

Details for the file streamlit_notification_center_component-0.0.3-py3-none-any.whl.

File metadata

File hashes

Hashes for streamlit_notification_center_component-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 1e66165258637293588b5d3e73cb53988fef6569547ee66098b6020d08e73cde
MD5 0734d695b335df4c8dc416cb2e190760
BLAKE2b-256 b0dbdea858885f71a5133ac5b65fab9eee545aa56f7082d1c3791e770cd254af

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page