Inside Storyline's Execute JavaScript Trigger
In Part 1 of my series, I covered the basics of how the browser works and how you access the Storyline Player inside the browser console. This second part will explore more technical questions such as:
- What is the user.js file Storyline creates and why is it important? Why is it not a good idea to modify it?
- How can you add simple JavaScript code such as creating a random variable, finding out the time of the day, or counting the words in a sentence?
- What are some of the fundamental mistakes learning designers make when adding JavaScript?
- How to handle more complex, longer JavaScript code using an external file?
- What are some ways of interacting with WebObjects?
- What is jQuery, and why are there so many Storyline code examples that are not working?
Why JavaScript?
Out of the box, Storyline comes with advanced features such as triggers and conditions. You can build sophisticated courses without using any additional JavaScript. Why would you even consider learning the basics of JavaScript?
- Level 1
With some simple copy-and-paste into a JavaScript trigger, you can accomplish things more easily than just using triggers. For example, multiple conditions (if-then-else) can be done in Storyline, but it is messy. You can reduce the number of triggers in your window from ten to one. - Level 2
Storyline does not have date/time functionality, string manipulation, or more sorting mechanisms. These are examples of what you can't do in Storyline unless you add some JavaScript. For example, knowing the date you may restrict a course or retire a link. Checking the number of words in a free-text input can be used in a condition for submitting the entry, etc. - Level 3
Level 1 and level 2 generally allow you to keep your code in Storyline JS trigger. It may not be easy to maintain but you can get away with it. Level 3 is for more sophisticated, longer code. Often the code is used from various triggers in Storyline, which means you will need to paste it multiple times in different slides. This is not a best practice. Troubleshooting can be a nightmare if the same code is duplicated over and over again, as you need to change it in multiple places. - Level 4
Level 4 is using external JavaScript libraries or building your own, working with APIs and iframes, etc. Building a two-way communication with an embedded WebObject can be powerful (for example, a mini-game inside the course that receives and sends information).
For Level 1 and Level 2, you don't need to be a programmer. In fact, you may not even need to code. All you need is to be able to ask the right questions and find the right people and resources. It is a good start before deciding if coding is for you. Learning JavaScript is a separate exercise. I have some recommendations at the end of the article [1].
What Happens To The JavaScript Code In Storyline?
In my previous article, we used the alert() function to pop up a message. Let's follow Storyline's publication process and see what happens to the JavaScript code:
- Create a new Storyline course.
- Add an Execute JavaScript trigger when the timeline reaches three seconds.
- Click the JavaScript link in the trigger to open the JS code window.
- Add the following code in the window:
let message = "Hello WORL&D!";
alert(message);
- Click OK twice and save.
- Publish your course locally on your drive for the web (you can also do LMS but for now we're digging into the files the publishing creates).
The code you entered will run three seconds after the page is loaded. You will see a message popup saying "Hello WORLD&D!." We use "let" to declare a JavaScript variable called "message." We set this variable to "Hello WORLD&D!," so we can use the variable name inside the alert function to display the message. (Alert sees that we're passing a variable name, so it looks up what the message variable is equal to and displays it in a popup.)
Where Is The Code?
Navigate to the published files you just created using a file explorer (by default in Windows this is File Explorer but most people use their own favorite. For example, the only software I've never replaced for daily tasks for 20 years is Total Commander).
You'll find a story_content folder within the main folder of the published files. If you open up that folder, you'll notice a file called user.js. This is a JavaScript file!
Anything you put in any Execute JavaScript trigger in Storyline will end up in the user.js file.
Tip: Open up the file with your code editor (you can use any text editor but I recommend Notepad++, Visual Code Studio, Sublime Text, or Atom. You do not want to use Microsoft Word or any fancy word processor.).
Inside this user.js is the code we added in the Storyline trigger. However, Articulate has added a twist that you need to be aware of.
If you == "Not interested deep geekiness" then skip this
In other words, you can skip this section if you don't care about the consequences of how Articulate stores JS code. Knowing that this is the place to find your code is sufficient for most people.
First of all, how does Storyline use this user.js file? Well, when you launch your course through index.html, there is a line there to load the user.js file so the content will be available any time throughout the course.
When I learned about this ages ago, my first thought was "This is excellent. I can add some code right here in the user.js directly and it will be automatically loaded every time." Unfortunately, this user.js is re-created every single time you publish the course so adding some code here directly it would be lost.
Second, let's take a look at the Articulate twist of how the code is used:
You can see the two lines of code (lines 13 and 14) that we put in an Execute JavaScript trigger. However, there's more stuff here. Our code is now wrapped inside function Script1() { ... }. Storyline automatically wraps all code you place in the window by a function Script1() { ... } where the number is incremental (Script1, Script2, Script3, etc.).
I'm going to oversimplify this because the article is not for advanced readers. Storyline needs to be able to execute the code when we tell Storyline to do this (in our case it's three seconds after the page is loaded), not when the user.js is file is loaded. Therefore, they wrap the code we placed inside Storyline in a function. Basically, a function (Script1) is a collection of code that belongs together. Storyline can execute the code by calling the name of the function: Script1(). This means every single trigger with JavaScript code will have a function in this file: Script1, Script2, Script3, etc. This course has only one JavaScript trigger so you know where it is in the Storyline file. However, imagine a multitude of these non-descript names for a real course. Without meaningful function names, it is challenging to figure out from here what slide the code actually belongs to.
And wait, there's more! Script1, Script2, Script3 are re-created every time you publish. This means what's now Script2 might become Script3 next time if you happen to add another JS trigger in Storyline. This fluidity means that we can't rely on the name of the function to identify where the code is in Storyline. Basically, this user.js is created for Storyline, not for us designers.
If the functions change with every publishing, how does Storyline know which code to run? That is the twist Articulate added cleverly. Storyline does not call the function itself. It calls the ExecuteScript() function (which is written by Articulate, not by you) and passes a special ID (62RBgMnPdW). Based on this special ID, the code calls the right function (Script1). It functions as a translation service.
Why Would We Use User.js If It Is Always Rewritten Anyway?
The user.js file can be used for troubleshooting. As Part 1 of this series explained, you can use the Console to display variables for troubleshooting. Since user.js holds all your code, you can modify the JavaScript code in user.js and then reload the course in the browser to test. You don't need to go back to Storyline with every test and republish your course. Once you're done with troubleshooting and have found the issues in the code, don't forget to change it in Storyline Execute JS trigger as well.
One Last Thing About Variables: Scope
In Storyline, every variable is a global variable because you have access to them on any slide. Storyline does not have local variables. A local variable would be available only on a specific slide but not on others. JavaScript, however, has both global and local scope for variables (and functions). For example, if you open the user.js file and on top of the file you add: let myvar = 10;, the myvar variable will be a global variable available anywhere in your script (including in our code: alert(myvar); will display a popup with the number 10). Since myvar is global, you can change its value from anywhere.
When you declare a variable inside a function (for example, our message variable is declared inside the Script1() function), then the variable is a local variable. Its scope only extends within the Script1() function. If there were other functions, the message variable would not be accessible; it would be out of scope.
How To Add Simple JavaScript Code (Such As Creating A Random Variable, Finding Out The Time Of The Day, Or Counting The Words In A Sentence)
Simple JavaScript means that you can "lift and shift" it from somewhere and paste it into a Storyline JS trigger. "Lift and shift" may not be a serious programming term but it describes the process well: You search for the code you need, lift it from the source, and then paste it into the Storyline JS trigger. This will be more like copy-and-paste. However, most of the time you need to slightly adjust it for your needs. That's the shift part.
For example, if you want to count the number of words in a Storyline variable, you search for the code. Make sure you give credit where credit is due. Most searches will take you to Stackoverflow. Stackoverflow is one of the best places to find your answer because somewhere, sometime someone has already asked your question, guaranteed. You will also learn that there isn't one way to solve a problem. In Stackoverflow, users upvote better, more effective, more efficient, or cross-browser solutions.
So, let's say you find this post about how to count the number of words:
function countWords(s){
s = s.replace(/(^s*)|(s*$)/gi,""); //exclude start and end white-space
s = s.replace(/[ ]{2,}/gi," "); //2 or more space to 1
s = s.replace(/n /,"n"); // exclude newline with a start spacing
return s.split(' ').filter(function(str){return str!="";}).length;
}
Minimal coding literacy is to be able to read the code and understand how to use it and slightly modify it. You may not understand how this works (it's using regex to find patterns in the text to remove characters and then it splits the text by space to return the length of the array), but you need to be able to use this function.
Let's say you have a Storyline variable that stores users' input (UserText). You want to count the number of words in this variable and then set the UserTextLength (number) variable. Based on this number, you will be able to decide if users have typed in enough words.
Programming is as much about logic as typing code. You need to build your logic first. There are 4 things that need to be done:
- Get the value of the UserText variable.
- Pass the value of the UserText variable to the countWords function.
- Receive the number of words from the countWords function.
- Save the number of words in the UserTextLength variable.
Assuming you have the UserText and UserTextLength variables created in Storyline, the code in JavaScript trigger could look something like this:
let player = GetPlayer(); // gets the Storyline player object so we can communicate with SL
let sText = player.GetVar("UserText"); // declares a JS variable, sText, and it stores the value of UserText in the JS variable
let nLength = countWords(sText); // declares the JS variable nLength and calls the function countWords with the argument of sText to count the words. The function wil return the length and it is stored in nLength.
player.SetVar("UserTextLength",nLength); // Setting the SL variable UserTextLength to nLength, the counted words in sText
function countWords(s){
s = s.replace(/(^s*)|(s*$)/gi,""); //exclude start and end white-space
s = s.replace(/[ ]{2,}/gi," "); //2 or more space to 1
s = s.replace(/n /,"n"); // exclude newline with a start spacing
return s.split(' ').filter(function(str){return str!="";}).length;
}
You have solved a problem that you couldn't have otherwise in Storyline.
Fundamental Mistakes To Avoid When Adding JavaScript
It is tempting to open an Execute JavaScript trigger in Storyline and start writing JavaScript code. This is the last place you want to write code. It does not have syntax support. It is also difficult to test your code because you have to publish the module every time you change the code. Use a dedicated tool such as Visual Code Studio, Sublime Text, or Atom. You can test your code before you place it inside Storyline using Codepen.io.
Be careful about variable names! Storyline variables and JavaScript variables are two different entities. While Storyline doesn't care much about uppercase or lowercase spelling, in JavaScript it matters. The JavaScript variables name and Name are two different variables.
If you place JavaScript code on the master layout in Storyline and then check it in the user.js file, you'll see that the code is actually repeated over and over again. Basically, the master layout trigger is copied to every single slide where the layout is used. Troubleshooting is not easy when you're not sure which code is running. If you do need to add JS code on the master layout, first make sure it's working well on a single page.
More Complex JavaScript Code
Simple "lift and shift" code can just be copied into the triggers with no problem. Once you write more complex programs, it is more practical to keep your code in a single external JavaScript file. To include an external JS file automatically, (so you don't have to do it manually after each publish), check out the examples in Project 99.
The examples also include sample code for two-way communication with embedded WebObjects. For example, an embedded HTML file showing a YouTube video can interact with your Storyline course. Two-way communication means that you can control the video and it can send you information such as time watched or completed. WebObject can include a mini-game, where the score is sent to your Storyline course, for example.
A Final Thought On jQuery
jQuery is a JavaScript library that used to be included with Storyline. Many of the example scripts and code Articulate eLearning Heroes posted in the last couple of years are not working anymore because jQuery is no longer bundled with Storyline (as of January 2020). You can recognize jQuery code as they include jQuery or $ in the JavaScript code (for example, $( document ).ready(function() {...).
If you still want to include jQuery for your purposes, you need to add some JS code in Storyline to include that automatically as you publish. You'll find the exact code on how to do it in Project 99.
This is article is Part 2 of a 2-part series. Read Part 1 here.
References:
[1] Learning JavaScript online:
- JavaScript Tutorial for Beginners: Learn Javascript Step by Step
- Learn JavaScript
- Learn JavaScript - A Free 7-hour Interactive Tutorial
- JavaScript Courses
Or offline: