Running Python Script from within Storyline - case study

Jul 14, 2023

Hei,

In this discussion I share how to run Python Script from within Storyline. We see how to get the system time using Python Script and display it in a Story slide as an example.

Try out the Review360 example: https://360.articulate.com/review/content/245359ba-e056-4224-b214-0c742bd3b9f2/review

You can find the Story for this example attached below. You might like to follow along with this discussion. Also attached below is a simple html page (pyscript print time.html) containing py-script that serves as a basis for this test Story. 

 

1. The first thing to point out is that py-script itself is presently a beta program. So, it is not yet recommended to include it in production items. See https://pyscript.net/

 

2. To run py-script code, a Story needs to load libraries and then the py-script  itself, before the code is ready to run. Let’s go through the slides to see how this is done.

 

2.1 We’ll start with slide 1.2 webObject

2.2 This slide is never run, but is a container for a webObject. That webObject contains files, bundled when the webObject is created, and included when the story is published. You can find a link in Resources below on how to build Stories that access the content of webObjects. 

2.3 The webObject is given the address of a sub-directory on my laptop when it is inserted into the slide. That sub-directory looks like this:

   …/webObject/

       |--> index.html

       |-->globals/

          |--> globalScripts.js

2.3.1 The index.html is empty in this case (but required for the webObject). (You can use it to, say, provide browser variables.) 

2.3.2 The sub-directory, globals, containing my global javascript code, globalScripts.js. This file contains global javascript code and also the py-script code. This global coe is available to all slides in a Story. We discuss this file here. It is also attached below for you to follow along with. 

 

2.4 When the Story runs, it starts on slide 1.1.

2.4.1 This slide is more or less a container to execute Javascript which dynamically loads the jquery library, the pyscript library, and my globalScripts.js personal library from the webObject on slide 1.2.

2.4.2 These are loaded asynchronously, and a Story variable is triggered when the dynamic loading is done.

2.4.3 It would be great if you could dynamically load a py-script file from here too. And indeed in principle you can – but trying to load a python script file dynamically triggers a CORS (a Cross Origin Response) violation. So sadly, we have to be more creative.

 

2.5 Enter Slide 1.3.

2.5.1 On start of slide 3, it executes a Javascript call to loadPython(). This function is found in my globalScripts.js file, which we loaded dynamically on slide 1.1.

2.5.2 loadPython essentially makes two calls, first to createPyScript(), the result of which is then passed to _runPython().

2.5.3 _createPyScript() contains the python code to be run. Note in particular the Python indentation rules apply, for example in the definition of current_time().

2.5.4 The python code to be run essentially does two things.

2.5.4.1 First it calls a Javascript function, _createObject(), also found in my globalScripts.js file. When this runs, it creates a Javascript variable, pyGlobals in this case, and ‘associates’ it to the supplied object, a globals() proxy.

2.5.4.2 (The globals() and create_proxy() functions are in the py-script libraries we loaded asynchronously on slide 1.1.)

2.5.4.3 This association results in the Python global variables and functions being made visible to Javascript. And we can invoke them from Storyline. You can find out more on this topic in the Resources below, pyscript-to-javascript. 

 

2.5.5.1 Second, my python code defines a function, current_time( ), to get the system time, and return it as a structured string. We will invoke this function when we click on the slide button.

2.5.5.2 Note all we do in createPyScript() is to create a javascript variable, pycode, containing this python code. You can create other functions like createPyScript() to contain different python code.

 

2.5.6 Our code isn’t run yet, that is where _runPython() comes in. This does two things.

2.5.6.1 Firstly, in assigning the variable div, _runPython creates an HTML ‘py-script’ element in the containing document (DOM). That containing document is the runtime Storyline.

2.5.6.2 It assigns the innerHTML value (or content) of that newly created div object with the pyCode we supply, wrapped up by <py-script>….</py-script> tags. The created div element is then placed into the html “body”.

2.5.6.3 Secondly, _runPython evaluates the div object. This is where the python interpreter will be invoked, to run our pyCode.

2.5.6.4 Once run, our python function current_time() will be made publically visible to our Javascript. This is indicated from within _createObject(), where a Storyline variable is set, which we use as a trigger to enable the slide 1.3 button.

2.5.7 And that completes loadPython().

 

2.5.8 Back on slide 1.3, when the Storyline button is pressed, this triggers  javascript to execute. This javascript makes a call to getTime(), also found in my globalScripts.js file.

2.5.8.1 getTime first locates the py-script function current_time() through the now visible pyGlobals object. And second invokes it, to get the system time.

 

Resources:

https://pyscript.net/ 

https://docs.pyscript.net/latest/ 

https://docs.pyscript.net/latest/guides/passing-objects.html#pyscript-to-javascript 

https://360.articulate.com/review/content/ff2e4ae0-051a-430b-a1b3-0b677eb6e63c/review How to build Stories with webObjects that contain files after you change the file content.

2 Replies