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

In addition to providing the streamlit component for use, the component comes with several useful python functions that make working with the component much easier. These functions are obviously provided to aid on the streamlit/python side of things.

nc_ensure()

The nc_ensure() function is provided to ensure that the environment is setup properly for use. It is heavily used by the internal functions and can be used in your own scripts. For example, if you wish to setup your use of notification_center() in a separate file, you might do something like this:

from notification_center import nc_ensure, nc_add_style

def nc_setup():
  # Ensure notification center globals are ready
  nc_ensure()
  
  # Add styles for use with nc_html()
  nc_add_style(f'* {{font-size:200%;}}')

This function would ensure that any poking or prodding to st.session_state._nc_ variables would be ready to go if this is something you needed to do.

nc_get(key, default_value=None)

The nc_get() function allows the retrieval of the array of messages sent to the key in question. If no value exists with the supplied key then the default_value, defaulting to None, is what is returned.

let button_presses = nc_get('button_clicks')
if button_presses:
  # do something

The value returned here will be a list or array of zero or more values sent to the key specified.

nc_get_last(key, default_value=None)

Similar to nc_get() however this function returns the last value in the list associated with the supplied key. If the list is empty or none exists with that key, then the default_value is returned.

let last_command = nc_get_last('executed_commands')
st.write(f'The last command executed was {last_command}')

nc_get_all()

This function returns the entire dictionary of values stored on the session state

let dictionary = nc_get_all()
# work on the dictionary

nc_set(key, value)

This is the value executed when postMessage() function is called and the NotificationCenter processes the contents successfully. The key value is used as a dictionary key and the value is appended to the array of previous messages for the same target.

nc_set('person', {'name': 'Brielle', 'gender': 'Female'})

nc_has(key)

This function returns True if the supplied key exists in the dictionary of NotificationCenter values, or False otherwise.

if nc_has('people'):
  people = nc_get('people')
  # do work on the people list

nc_clear(key)

This function will locate an existing key in the dictionary. These keys have a list as their value. The .clear() function will be invoked on the list.

# User clicked on delete all people
nc_clear('people')

nc_clear_all()

This function removes all keys and values from the NotificationCenter data store.

nc_clear_all()

nc_add_script(script)

The function nc_html() is a more function version of the st.components.v1.html(). The nc_html() function will pre-inject CSS, JavaScript and any HTML snippets previously setup as global pre-defines. The nc_add_script() function allows you to add a snippet of JavaScript to all nc_html() calls before the supplied HTML string.

nc_add_script(
  f'''
  Object.defineProperty(Array.prototype, 'first', { get: function() { return this?.[0] } })
  '''
)

After running this function, the next time nc_html() is invoked, the component added to the page will have access to JavaScript arrays with a getter on each instance called .first that dynamically returns the first value or null if there isn't one.

nc_add_style(style)

The function nc_html() is a more function version of the st.components.v1.html(). The nc_html() function will pre-inject CSS, JavaScript and any HTML snippets previously setup as global pre-defines. The nc_add_style() function allows you to add a snippet of CSS to all nc_html() calls before the supplied HTML string.

nc_add_styles(f'* {{ font-size: 200%; }}')

When executed, all subsequent nc_html() calls will find their contents' font sizes increased to 200%.

nc_add_html(html)

This function adds a new snippet of html that will be included in all calls to nc_html().

nc_add_html(f'<hr/>')

When next nc_html() is executed, it will always be preceded by a horizontal rule.

nc_reset_scripts()

Resets all JavaScript snippets to the defaults. The defaults are the three aforementioned JavaScript Helper functions.

nc_reset_styles()

This function removes all globally added CSS snippets previously added using nc_add_style().

nc_reset_html()

Simlar in function to nc_reset_scripts() or nc_reset_styles(), the function nc_reset_html() removes all previously added HTML snippets.

nc_listen(to, callback)

Probably one of the most useful function, this call will execute the supplied callback whenever any new value is set with a key/target that matches the to value supplied to this function.

unsub = nc_listen('add_person', lambda person: print(person))
# when done
unsub()

This function also returns a function or Callable that when invoked will trigger an unsubscribe on the internal event manager.

nc_html(html, extra_js=[], extra_css=[], width=None, height=None, scrolling=False)

The creme of the crop, injects a component of HTML into the page. This HTML iframe that gets injected will be prefixed by any previous calls to nc_add_html(), nc_add_style(), and nc_add_script(). Even when no other code is injected, at least the three helper JavaScript functions above will be present. This allows simple code like this to work:

nc_html(f"<a onclick=\"ncSendMessage('target', {{...}})\">Send Message</a>")

Future additions to this functionality will likely be things like nc_add_script_src() or nc_add_stylesheet() to make this functionality a bit more succint and rely on the browser's ability to cache.

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

from notification_center import notification_center, nc_html, nc_add_style, nc_listen

notification_center(key="globalnc")

nc_listen('data', lambda data: print('data added', data))

nc_add_script(
  f'''
  function sendData() {{ ncSendMessage('data', {{msg: 'Stylized Button Pressed'}}) }}
  '''
)

nc_add_style(
  f'''
  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;        
  }}
  '''
)

nc_html(f'<a class="stylized-button" onclick="sendData()">Button</a>')

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 is being used 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.4.tar.gz (454.9 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.4.tar.gz.

File metadata

File hashes

Hashes for streamlit_notification_center_component-0.0.4.tar.gz
Algorithm Hash digest
SHA256 aedfeb20fcd6985b59a54c6db4161ddc01051bec88dae1da54d12f370f28e02a
MD5 a6fa293f92feca84e52de5a90da10a17
BLAKE2b-256 3a1adf3bf7021bb900c3fd86a9aa73e493077197b47b34d81819befce93f86ee

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for streamlit_notification_center_component-0.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 130b1dbb32a3d42cb76a40fda39e47c4d53caa278b240beaf38c399199e9c2ba
MD5 f3c47eb432cbd130948d09028acee9ca
BLAKE2b-256 b8da812dbf76d72fefcd8459fcb6faf05f139ba796000170cfafd843da0aa1c1

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