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.2.tar.gz (449.0 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.2.tar.gz.

File metadata

File hashes

Hashes for streamlit_notification_center_component-0.0.2.tar.gz
Algorithm Hash digest
SHA256 be52e0c289d49f99dbbee5c7ea859fee36f30a214c3c0059eda85805e35868c3
MD5 645ecf54db4f522cafa927ed886451e2
BLAKE2b-256 5092b7cf28405b0355b62f0e56d265b06cce51ccdce6b4167b1750e9592c55db

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for streamlit_notification_center_component-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 e09e4b78729f60833890847d04d496888d4b8ef7af1bd26031dbaae2c32201aa
MD5 12fd669a9cd04cd7fb8af8e952bb212b
BLAKE2b-256 bc91d197d91f6238fb87cac7cba7bded21821856fcb91ad38f96b793b759057e

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