Shiny for Python and JavaScript: How to Add JS Scripts to Your Dashboards
Web developers love JavaScript. And who can blame them, it's a fantastic do-it-all language for making stunning websites. But what about <b>Shiny</b> and <b>Shiny for Python</b>? As it turns out, you can add JavaScript scripts to your Shiny for Python dashboards without too much trouble. Today you'll learn how to add JS scripts to Shiny for Python by making a simple app. All it does is ask the user for its name after clicking on a button, and then render the name or display a cancellation message. Truth be told, you can code the same app in Python/R, but we thought this would be a neat example for potential JavaScript usage. <blockquote>What are out thoughts on Shiny for Python? <a href="https://appsilon.com/pyshiny-demo/" target="_blank" rel="noopener">Take a look at this First Impressions article</a>.</blockquote> Table of contents: <ul><li><a href="#introduction">Shiny for Python and JavaScript: Let's Code a Basic App</a></li><li><a href="#static-files">How to Serve Static Files in Shiny for Python</a></li><li><a href="#javascript">How to Add JavaScript Scripts to Shiny for Python Dashboards</a></li><li><a href="#summary">Summary of Shiny for Python and JavaScript</a></li></ul> <hr /> <h2 id="introduction">Shiny for Python and JavaScript: Let's Code a Basic App</h2> First things first, we need an app. The good news is that it will take less than 10 lines of code to have it up and running. Open any code editor and create an <code>app.py</code> file. The entire UI will have only three elements: <ol><li><b>Title</b> - It's just an <code>h3</code> element, not really important.</li><li><b>Button</b> - It'll open a prompt when clicked, but more on that later.</li><li><b>Paragraph</b> - For displaying the content entered by the user. It's blank by default, and we care only about the <code>id</code>.</li></ol> The <code>server()</code> function will remain empty this time, as we won't handle any logic with Python. Here's the full code snippet for our simple app: <pre><code class="language-python">from shiny import App, ui <br> app_ui = ui.page_fluid( ui.h3("Shiny for Python - JavaScript Example"), ui.input_action_button(id="btn", label="Click me!"), ui.p(id="prompt_text") ) <br> def server(input, output, session): pass <br> app = App(ui=app_ui, server=server)</code></pre> Run the following command from the shell to run the app: <pre><code class="language-shell">shiny run --reload app.py</code></pre> It will run on <code>localhost:8000</code> by default: <img class="size-full wp-image-15594" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d7ca19357fedd7b53204_262ccb69_1.webp" alt="Image 1 - Our basic Shiny for Python app" width="2310" height="1806" /> Image 1 - Our basic Shiny for Python app Nothing happens when you click on the button, which is expected. Before we can tie it to some programming logic, we have to discuss serving static files in Shiny. <h2 id="static-files">How to Serve Static Files</h2> Think of <b>static files</b> as an umbrella term for images, CSS files, and scripts. Basically, it's anything somewhat related to your app that isn't written in the main file. A common practice is to create a <code>www</code> folder and put everything mentioned inside it. Please create one yourself and make the following modifications to <code>app.py</code>: <ul><li>Import the <code>Path</code> class from the <code>pathlib</code> library.</li><li>Create a variable <code>www_dir</code> that will store an absolute path to the <code>www</code>directory.</li><li>Inside <code>App()</code>, specify an additional parameter <code>static_assets</code> and set it to <code>www_dir</code>.</li></ul> Here's the entire modified snippet in case you got lost in the process: <pre><code class="language-python">from shiny import App, ui from pathlib import Path <br> app_ui = ui.page_fluid( ui.h3("Shiny for Python - JavaScript Example"), ui.input_action_button(id="btn", label="Click me!"), ui.p(id="prompt_text") ) <br> def server(input, output, session): pass <br> www_dir = Path(__file__).parent / "www" app = App(ui=app_ui, server=server, static_assets=www_dir)</code></pre> The app still looks the same, even if you click on the button: <img class="size-full wp-image-15596" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d7cbd7575130662eae6b_40f7779a_2.webp" alt="Image 2 - Our basic Shiny for Python app (2)" width="2310" height="1806" /> Image 2 - Our basic Shiny for Python app (2) We now have the static assets available in the Shiny for Python app, so next, we can finally add a JavaScript file. <h2 id="javascript">How to Add JavaScript Scripts to Shiny for Python Dashboards</h2> Let's take a short break from Python and focus on JavaScript. Create a <code>script.js</code> file inside the <code>www</code> directory, and inside it declare a <code>runJS()</code> function. This function will be executed when the button is clicked. The function logic is straightforward - It prompts the user to enter its name, which is then displayed in the below paragraph. If the user cancels the prompt, an appropriate message is shown instead: <pre><code class="language-javascript">function runJS() { let text; let user = prompt("Please enter your name:", "First name"); <br> if (user == null || user == "") { text = "Prompt cancelled by the user."; } else { text = "Hello " + user + "!"; } <br> document.getElementById("prompt_text").innerHTML = text; }</code></pre> <b>But how can you now tie the button to the function?</b> Well, it's a two-step process: <ol><li>Add <code>script.js</code> to the document's <code>head</code>.</li><li>Add an <code>onclick</code> property to the button and set its value to <code>runJS()</code> - This will run the function when the button is clicked.</li></ol> If you prefer Python over English, here's everything you need: <pre><code class="language-python">from shiny import App, ui from pathlib import Path <br> app_ui = ui.page_fluid( ui.tags.head( ui.tags.script(src="script.js") ), ui.h3("Shiny for Python - JavaScript Example"), ui.input_action_button(id="btn", label="Click me!", onclick="runJS()"), ui.p(id="prompt_text") ) <br> def server(input, output, session): pass <br> www_dir = Path(__file__).parent / "www" app = App(ui=app_ui, server=server, static_assets=www_dir)</code></pre> Refresh the app and click on the button - You'll see the prompt like the one below: <img class="size-full wp-image-15598" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d7cc019bb567ab86768c_0269518d_3.webp" alt="Image 3 - Entering a value in the prompt" width="2310" height="1806" /> Image 3 - Entering a value in the prompt As soon as you hit the OK button, the paragraph text gets populated: <img class="size-full wp-image-15600" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d7cdc5eef432cde442af_7bb8fac6_4.webp" alt="Image 4 - Shiny for Python app with the populated paragraph text" width="2310" height="1806" /> Image 4 - Shiny for Python app with the populated paragraph text But what if you cancel the prompt? Click on the button and hit Cancel instead: <img class="size-full wp-image-15602" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d7ce16b3ff510e39f3f5_7d52b252_5.webp" alt="Image 5 - Paragraph value when the prompt is canceled" width="2310" height="1806" /> Image 5 - Paragraph value when the prompt is canceled Long story short, that's how you can add JavaScript to your Shiny for Python apps. Let's wrap things up next. <hr /> <h2 id="summary">Summary of Shiny for Python and JavaScript</h2> Adding JavaScript to Shiny for Python apps comes with an important prerequisite, and that is creating and linking the static assets directory. That is if the JS file you plan to use is stored locally. If not, simply add a web url to the <code>src</code> parameter. We hope this article has served you as the basis for more advanced JavaScript use cases in Shiny for Python. Let us know in the comment section below what are your thoughts on this integration, and how you plan to use JS in your apps. Also, feel free to continue the discussion on Twitter - <a href="https://twitter.com/appsilon" target="_blank" rel="noopener">@appsilon</a>. We'd love to hear from you. <blockquote>Take a break from Python - <a href="https://appsilon.com/webscraping-dynamic-websites-with-r/" target="_blank" rel="noopener">Learn to scrape dynamic websites in R instead</a>!</blockquote>