Extract variable from html component for further use in python script

Hi,

I want to create a slider and a colored box that changes brightness depending on slider position (i.e.: slider at 0= white, slider at 100=black and shades of grey inbetween).
The straightforward implementation with streamlit’s slider was suboptimal because everytime the slider was used, the code reran making it a pain to use because the colored box didn’t update immediately (a smooth transition is very important in this use case).

I tried to create the same thing using javascript and put it in a html component:

import streamlit as st
import streamlit.components.v1 as components


html_code = f"""
<html>
  <body>
    <input type="range" id="brightnessSlider" min="0" max="100" value="50" onchange="updateColor()">
    <div id="colorBox" style="width: 200px; height: 50px; background-color: rgb(0,0,0);"></div>
    <script>
      function updateColor() {{
        var sliderValue = document.getElementById('brightnessSlider').value;
        var grayValue = 255 - (sliderValue * 2.55);
        document.getElementById('colorBox').style.backgroundColor = 'rgb(' + grayValue + ',' + grayValue + ',' + grayValue + ')';
      }}
      updateColor();
    </script>
  </body>
</html>
"""

components.html(html_code, width=200, height=250)

This works great, the only problem is now: How do I access the sliderValue variable so that I can use it in my script for other stuff? I understand the html component can access python variables, but how can the python code access the html variables?

I have seen this tutorial but for some reason I cannot recreate the example project. At the same time it seems a bit overkill considering that I just need one small simple thing.

Thanks!

You can use Streamlit Components API for this specific purpose

1 Like

Yeah you’ll have to go deeper into the Components API to get back data, but you’re almost there!

2 Likes

Hey andfanilo,

this is great thank you! I have frankensteine’d that code and the JS-Python communication works. But unfortunately I realized that the page seems to refresh now everytime I use the html slider :cry: Do you know of a way to fix that?

Python code:

import streamlit as st
import streamlit.components.v1 as components

# Declare the custom component
mycomponent = components.declare_component(
    "mycomponent",
    path="./mycomponent"
)

# Initial slider value (50 in this example)
slider_value = mycomponent(my_input_value=50)

# Display the slider value in Streamlit
st.write("Slider Value:", slider_value)

HTML/JS code:

<html>
  <body>
    <!-- Set up your HTML here -->
    
    <input type="range" id="brightnessSlider" min="0" max="100" step= "1" value="50" onchange="updateColor()">
    <div id="colorBox" style="width: 200px; height: 50px; background-color: rgb(0,0,0);"></div>
    <script>
      // ----------------------------------------------------
      // Just copy/paste these functions as-is:

      function sendMessageToStreamlitClient(type, data) {
        var outData = Object.assign({
          isStreamlitMessage: true,
          type: type,
        }, data);
        window.parent.postMessage(outData, "*");
      }

      function init() {
        sendMessageToStreamlitClient("streamlit:componentReady", { apiVersion: 1 });
      }

      function setFrameHeight(height) {
        sendMessageToStreamlitClient("streamlit:setFrameHeight", { height: height });
      }

      // The `data` argument can be any JSON-serializable value.
      function sendDataToPython(data) {
        sendMessageToStreamlitClient("streamlit:setComponentValue", data);
      }

      // ----------------------------------------------------
      // Now modify this part of the code to fit your needs:

      var mySlider = document.getElementById("brightnessSlider");
      
      function updateColor() {{
        var sliderValue = document.getElementById('brightnessSlider').value;
        var grayValue = 255 - (sliderValue * 2.55);
        document.getElementById('colorBox').style.backgroundColor = 'rgb(' + grayValue + ',' + grayValue + ',' + grayValue + ')';
      }}
      // Call the function initially to set the initial color
      updateColor();


      // Function to send slider value to Streamlit
      function onSliderChange() {
        sendDataToPython({
          value: mySlider.value,
          dataType: "json",
        });
      }

      function onDataFromPython(event) {
  if (event.data.type !== "streamlit:render") return;
  // Access data sent from the Streamlit app here, if needed
}

      mySlider.addEventListener("input", onSliderChange);

      // Hook things up!
      window.addEventListener("message", onDataFromPython);
      init();

      // Hack to autoset the iframe height.
      window.addEventListener("load", function () {
        window.setTimeout(function () {
          setFrameHeight(document.documentElement.clientHeight);
        }, 0);
      });

      // Optionally, if the automatic height computation fails you, give this component a height manually
      // by commenting out below:
      //setFrameHeight(200);
    </script>
  </body>
</html>

Hey! Great to hear :slight_smile:

I think, from memory, that you need to add a key argument like mycomponent(my_input_value=50, key="slider") like for other widgets for it to not refresh on each rerun

2 Likes

Thank you for the tip! I have tried adding it but it still refreshes
 any other ideas?

Hmmm this is odd, it seems to work on my side? Is the following GIF what you expect?

test

Do you have other code pushing for a refresh? What Streamlit version/browser are you using?
Are you seeing odd things in the devtools console tab of your browser?

Or maybe you mean the page does a rerun at every slider interaction, which in that case yes, every slider interaction will do a Streamlit rerun, like a basic st.slider.

EDIT: ah, I just reread your initial problem. Note that it is the sendDataToPython call that reruns the Streamlit app, but you don’t have to run it immediately, maybe you can put a button next to the slider, and only sendDataToPython on the button click but keep the updateColor on slider change

1 Like

Yes it finally works! I have created a button that submits the slider value and now everything is smooth this is amazing :slight_smile: I have been wrestling with this issue for ages!

I have a last difficulty though: I need to find a way to register whether the button was clicked in the python script so that I can access both, the slider value st.write("Slider Value:", slider_value) as well as the buttonPress:st.write("buttonPressed: ", buttonPress) (where buttonPress is True/False for example)

I need this information to trigger further calculations in my python script as soon as the person clicks submit. I tried adding it to the sendDataToPython() function but so far unsuccessfully


Updated Python code so far:

import streamlit as st
import streamlit.components.v1 as components

mycomponent = components.declare_component(
    "mycomponent",
    path="./mycomponent"
)

slider_value = mycomponent(my_input_value=50, key="slider") 
st.write("Slider Value:", slider_value)

Updated HTML/JS code:

<html>
  <body>
    <!-- Set up your HTML here -->
    
    <input type="range" id="brightnessSlider" min="0" max="100" step= "1" value="50" onchange="updateColor()">
    <div id="colorBox" style="width: 200px; height: 50px; background-color: rgb(0,0,0);"></div>
    <button id="submitButton">Log answer</button>
    <script>
      // ----------------------------------------------------
      // Just copy/paste these functions as-is:

      function sendMessageToStreamlitClient(type, data) {
        var outData = Object.assign({
          isStreamlitMessage: true,
          type: type,
        }, data);
        window.parent.postMessage(outData, "*");
      }

      function init() {
        sendMessageToStreamlitClient("streamlit:componentReady", { apiVersion: 1 });
      }

      function setFrameHeight(height) {
        sendMessageToStreamlitClient("streamlit:setFrameHeight", { height: height });
      }

      // The `data` argument can be any JSON-serializable value.
      function sendDataToPython(data) {
        sendMessageToStreamlitClient("streamlit:setComponentValue", data);
      }

      // ----------------------------------------------------
      // Now modify this part of the code to fit your needs:

      var mySlider = document.getElementById("brightnessSlider");
      var submitButton = document.getElementById("submitButton"); // Reference to the Submit button
      
      function updateColor() {{
        var sliderValue = document.getElementById('brightnessSlider').value;
        var grayValue = 255 - (sliderValue * 2.55);
        document.getElementById('colorBox').style.backgroundColor = 'rgb(' + grayValue + ',' + grayValue + ',' + grayValue + ')';
      }}
      // Call the function initially to set the initial color
      updateColor();



      submitButton.addEventListener("click", function () {
        sendDataToPython({
          value: mySlider.value,
          dataType: "json",
        });
      });


      function onDataFromPython(event) {
  if (event.data.type !== "streamlit:render") return;
  // Access data sent from the Streamlit app here, if needed
}



      // Hook things up!
      window.addEventListener("message", onDataFromPython);
      init();

      // Hack to autoset the iframe height.
      window.addEventListener("load", function () {
        window.setTimeout(function () {
          setFrameHeight(document.documentElement.clientHeight);
        }, 0);
      });

      // Optionally, if the automatic height computation fails you, give this component a height manually
      // by commenting out below:
      //setFrameHeight(200);
    </script>
  </body>
</html>
1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.