Forum Discussion
Global scope for Javascript variables in Storyline
As Storyline publishes all code and variables to separate functions in the user.js file the scope for individual variables is local. The solution i show here creates a global scope for functions and variables so you can reuse your Javascript code and variables anywhere in a Storyline project.
Basically i make use of a WebObject to add an external Javascript to Storyline. In that script variables are stored globally so you can reuse them anytime. Also with this solution you can add any custom function to the globalScript.js file. Those functions can then be called from any spot in Storyline.
Lets see it working first.
https://360.articulate.com/review/content/d02ea9a4-b779-4447-befa-7af5a2d02e21/review
In this Storyline you can enter 2 values. A property name and its value. Those will be saved in a Javascript object and can be called with a global function or directly. When adding your first one... it shows in the textfield on the left... then you can continue to the next slide...and do the same... either edit one of the added values...or add a new one.
The function iterateObject(_obj,_desiredoutput); you can use to get your variables and values. Offcourse the argument _obj is for your Object in Javascript. In this case its 'globObj', but you can add as many as you want and customize them. The argument _desiredoutput is for what you want returned. Default that is a string, but you can choose to get an array.
Alright some more info on how it works. As you can see in the Storyline file i use a separate scene for the WebObject that loads the Javascript file(s).
As you can see i use subfolders for my Javascript files. Nothing more then the empty index.html and the globalScripts.js in its own subfolder for the WebObject.
To load the Javascripts files into Storyline i use a Javascript trigger on the start of the timeline. This is probably best added to a Master Slide and ensure its only loaded when the variable 'javascriptsLoaded' is False.
When changing anything to the 'globalScripts.js' or the WebObject you would need to change 2 variables in the script. amountOfLibs and webObjectURL. The latter offcourse refers to the folder Storyline publishes the webObject to. And you would need to publish and check the name of the folder when changing anything.
Then you can call the functions from the 'globalScript.js'. In this sample i only added 2 functions. One to set the variables and one to get them again. Both work on any spot in the Storyline. After the Javascript is loaded you can set and get variables from anywhere in your Storyline.
Do hope this is usefull for some of you.
26 Replies
- EmmanuelNdlecCommunity Member
Ok, I finally found my error : the "getGlobalValue" function shouldn't return
return obj2Use[_prop].value;
but
return obj2Use[_prop];
and for the record, I think Math wrote it, but I initially didn't find it in a old version of globaScript.js so that's why I re-wrote it.
I must have messed it long ago.
Lesson learned : backup and name your files / folders properly, don't rely on your memory 😆 - EmmanuelNdlecCommunity Member
(2nd try to post, don't know why 1st disappeared)
Hi everyone,
One year later and I'm asked to build a tool that helps tracking user progress, but not through how many slides have been seen/accessed as Storyline allows it, but through how many buttons have been clicked (inside a list of which buttons to track) inside several slides.
This tool (set of scripts) will be used by co-workers who have no skill at all in coding, si I try to make it as easy as possible to use.
I first made an Excel sheet that contains a table they have to fill. The table goes like this :slide1name | slide2name | etc. button1A | button2A | etc. button1B | button2B | etc. | button2C |
first line contains the slide names we want to track (not storyline names or references, but decided by my co-worker)
Then for each slide name, the column below contains the names of the buttons whose click we want to track (once again, those names are not from storyline but decided by my co-worker).Then I use in excel formulas to create the javascript line that declares a multidimensionnal array :
[[list of slide names],[[[list of button names of 1st slide],[list of their states]],[ (same for slide 2) ],[etc.]]]for example, with 3 slides named "sliA", "sliB" and "sliC" ; sliA containing 2 buttons to track, sliB 3 buttons and sliC 4 buttons, the js declaration of the array is
let tabProgress=[['sliA','sliB','sliC'],[[['butA1','butA2'],[0,0]],[['butB1','butB2','butB3'],[0,0,0]],[['butC1','butC2','butC3','butC4'],[0,0,0,0]]]];
my idea is to call javascript functions I place in a js file loaded thank's to Math's globalScript solution :
- to get or set the state for any given button in any given slide :
- I search for the slide name in first sub-array, get it's position
- I search for the sub-sub array in 2nd sub-array at the same position
- I search for the button name if the first array of the sub-sub array, and get or set the value in the 2nd one at the same position.
- to calculate the percentage of buttons clicked for a given slide,
- to calculate the percentage of buttons clicked for all slides.
I added a single "getGlobalValue" function to Math's globalScript.js, just to get my array before passing it to the above functions.
Here's the modified globaScript.js file :console.log("globalScripts.js loaded"); /* globObj is a global Object to save data too */ const globObj = {}; // we could use multiple objects and pass/read data const semiGlobObj = {}; /* Global functions that can be called from anywhere in Storyline. Any needed functions can be added here and you can call them directly in Storyline. */ /* This function creates properties on any given object that then can be reused anywhere. Eval is needed to get the proper Object and bracket notation [] is needed to convert the input to a literal name for the property */ function setGlobalValue(_val,_prop,_obj){ console.log("set: "+_obj+" | "+_prop); let obj2Use = eval(_obj); Object.defineProperties(obj2Use, { [_prop]: { value: _val, writable: true } }); console.log("set: "+_obj+" | "+_prop+" = "+obj2Use[_prop]); } /* added by E.Nédélec */ function getGlobalValue(_prop,_obj) { console.log("get: "+_obj+" | "+_prop); obj2Use = eval(_obj); console.log ("return : " + obj2Use[_prop].value); return obj2Use[_prop].value; } /* Function to loop and iterate an Object in Storyline. Returns a string or an array depending on chosen _desiredoutput */ function iterateObject(_obj,_desiredoutput){ const propsArr = Object.getOwnPropertyNames(_obj); let valuesArr = []; console.log(propsArr); let str = ""; for(var i = 0; i < propsArr.length; i++){ let prop = propsArr[i]; console.log("prop: "+prop); let val = globObj[prop]; valuesArr.push(globObj[prop]); console.log("val: "+val); str += propsArr[i]+" "+val+"\n"; } if(_desiredoutput=="array"){ return propsArr }else{ return str; } }
When I run my projet, here's what appears in the browser's console :
globalScripts.js loaded globalScripts.js:1:9 1 JS / story_content/WebObjects/6Tdp61PnyWP/globals/globalScripts.js loaded. user.js:28:15 jsProgess.js loaded jsProgress.js:1:9 2 JS / story_content/WebObjects/6Tdp61PnyWP/globals/jsProgress.js loaded. user.js:28:15 set: globObj | tabProgress globalScripts.js:21:17 set: globObj | tabProgress = sliA,sliB,sliC,butA1,butA2,0,0,butB1,butB2,butB3,0,0,0,butC1,butC2,butC3,butC4,0,0,0,0 globalScripts.js:29:17 get: globObj | tabProgress globalScripts.js:35:10 return : undefined
So my array seems properly set in the global object, my "getGlobalValue" function seems properly called, but why does it return "undefined" ?
This is driving me nuts 😣 - to get or set the state for any given button in any given slide :
- EmmanuelNdlecCommunity Member
Hi everyone,
One year later and I'm asked to build a tool that helps tracking user progress, but not through how many slides have been seen/accessed as Storyline allows it, but through how many buttons have been clicked (inside a list of which buttons to track) inside several slides.
This tool (set of scripts) will be used by co-workers who have no skill at all in coding, si I try to make it as easy as possible to use.
I first made an Excel sheet that contains a table they have to fill. The table goes like this :slide1name | slide2name | etc. button1A | button2A | etc. button1B | button2B | etc. | button2C |
first line contains the slide names we want to track (not storyline names or references, but decided by my co-worker)
Then for each slide name, the column below contains the names of the buttons whose click we want to track (once again, those names are not from storyline but decided by my co-worker).
Note that the number of slides and the number of buttons per slide may change from one project to the other (I based my excel on a 20 slides / 20 buttons per slide max, which is far enough).
Then I use in excel formulas to create the javascript line that declares a multidimensionnal array :
[[list of slide names],[[[list of button names of 1st slide],[list of their states]],[ (same for slide 2) ],[etc.]]]for example, with 3 slides named "sliA", "sliB" and "sliC" ; sliA contains 2 buttons to track, sliB 3 buttons and sliC 4 buttons, the js declaration of the array is
let tabProgress=[['sliA','sliB','sliC'],[[['butA1','butA2'],[0,0]],[['butB1','butB2','butB3'],[0,0,0]],[['butC1','butC2','butC3','butC4'],[0,0,0,0]]]];
my idea is to call javascript functions I place in a js file loaded thank's to Math globalScript solution :- to get or set the state for any given button in a given slide,
- to calculate the percentage of buttons clicked for a given slide,
- to calculate the percentage of buttons clicked for all slides.
I added a simple "getGlobalValue" function to Math's globalScript.js to get the array before passing to my functions.
Here's the globalScript file content :
console.log("globalScripts.js loaded"); /* globObj is a global Object to save data too */ const globObj = {}; // we could use multiple objects and pass/read data const semiGlobObj = {}; /* Global functions that can be called from anywhere in Storyline. Any needed functions can be added here and you can call them directly in Storyline. */ /* This function creates properties on any given object that then can be reused anywhere. Eval is needed to get the proper Object and bracket notation [] is needed to convert the input to a literal name for the property */ function setGlobalValue(_val,_prop,_obj){ console.log("set: "+_obj+" | "+_prop); let obj2Use = eval(_obj); Object.defineProperties(obj2Use, { [_prop]: { value: _val, writable: true } }); console.log("set: "+_obj+" | "+_prop+" = "+obj2Use[_prop]); } /* added by E.Nédélec */ function getGlobalValue(_prop,_obj) { console.log("get: "+_obj+" | "+_prop); obj2Use = eval(_obj); console.log ("return : " + obj2Use[_prop].value); return obj2Use[_prop].value; } /* Function to loop and iterate an Object in Storyline. Returns a string or an array depending on chosen _desiredoutput */ function iterateObject(_obj,_desiredoutput){ const propsArr = Object.getOwnPropertyNames(_obj); let valuesArr = []; console.log(propsArr); let str = ""; for(var i = 0; i < propsArr.length; i++){ let prop = propsArr[i]; console.log("prop: "+prop); let val = globObj[prop]; valuesArr.push(globObj[prop]); console.log("val: "+val); str += propsArr[i]+" "+val+"\n"; } if(_desiredoutput=="array"){ return propsArr }else{ return str; } }
Now what's driving me nuts is that I can get my array back. Here's what I read in the console :
globalScripts.js loaded globalScripts.js:1:9 1 JS / story_content/WebObjects/6Tdp61PnyWP/globals/globalScripts.js loaded. user.js:28:15 jsProgess.js loaded jsProgress.js:1:9 2 JS / story_content/WebObjects/6Tdp61PnyWP/globals/jsProgress.js loaded. user.js:28:15 set: globObj | tabProgress globalScripts.js:21:17 set: globObj | tabProgress = sliA,sliB,sliC,butA1,butA2,0,0,butB1,butB2,butB3,0,0,0,butC1,butC2,butC3,butC4,0,0,0,0 globalScripts.js:29:17 get: globObj | tabProgress globalScripts.js:35:10 return : undefined
The array seems properly stored through Math's function "setGlobalValue", as shown by "set" message in the console).
My "getGlobalValue" function is called as we can see "get: globObj | tabProgress".
But why does it return "undefined" ?
- BrettHansen-f01Community Member
Thanks Math for the global JS load and direction, this saved me a lot of time! Cheers!
- EmmanuelNdlecCommunity Member
Thank you very much @Math for all these explanations and examples, they really clarified my understanding.
It works like a charm now, and gives me access to new universes of possibilities !
- EmmanuelNdlecCommunity Member
For anyone reading this in the future : In addition to what Math spotted (the "go to slide" instruction that prevents previous javascript call) many of my difficulties here were because I had several errors in my script that immediatly disables all javascript in the project. The slightest missing ";" at an end of line can cause this.
In my case, I used also "let" declaration for a temp variable I wanted to declare & initialize several times in the same javascript call. At the very moment a 2nd "let" line for the same variable appeared in my script, nothing worked anymore.
I didn't yet sort the "Storyline goes too quick to the next slide" problem properly : actually I stop the chronology of the current slide, and when the user clicks "next" button I call my js fucntions and let the slide carry on : it ends 1 second later, with automatic go to next slide. And this seems enough for letting js call to be done.
I have a last question @Math : can your solution work with a 4D array as mine ? I would like to try to initialize my array from within a javascript inside my storyline project rather than from globaScript.js file, that would prevent the WebObjectURL value to change each time the content of my array has changed.- MathNotermans-9Community Member
Just made a sample showing proper workflow for creating multidimensional arrays and gettting the data from it.
https://360.articulate.com/review/content/7c1e0e02-77c8-4bb5-a632-21c21d10f7f3/review
Clicking the create multi array button creates a 3-dimensional array.
You can change the inputs for the depth of the array and the 2 inputs from 0-2 showing a value in the appropriate cell.
Clicking show value from array shows the appropriate value from the multidimensional array.
In fact clicking either button will create that multidimensional array from the variable showing... 'cows,horse,monkey,grass...' etc. As this variable now is only 9 items long, changing the dimensions of the multiarray to more then 3 will result in getting 'null' as the cells will be empty.
But basically this has all to create your own multidimensional arrays.
Kind regards,
Math - MathNotermans-9Community Member
Basically you donot need to use the globalScript approach. And yes it works fine with a multidimensional array ( 4D array is a multidimensional array with 4 dimensions ).
You can even use pure Javascript Storyline triggers and set a Storyline variable to hold your array.
- EmmanuelNdlecCommunity Member
Thank you so much, I'm going to look closely at this variable state change. I confirm that the message "appel de score" is from inside the js function that returns the wanted score value.
The last slide is actually totally temporary. In the end it will display the 10 scores using 10 sliders objects that I make non-interactive by placing an invisible button above them, and that I preset whit the score variable value.
- MathNotermans-9Community Member
So one thing eludes me... all seems to work, but im unclear how the 10 variables on the last page... sRes,sAdo up to sFai are set and used ?
- MathNotermans-9Community Member
So testing your file...and disabling the next button on the 2nd slide i nicely get this console.log...
Appel de score : sit=1 , quest=1 , rep=1 , comp=1
val= 1 | valeur prec de sRes=0
On the suite button i changed the console.log to this...console.log("val= "+val+" | valeur prec de sRes=" + prec);
So apparently ( correct me if im wrong ) you get all proper data from your multidimensional array and all logs fine.
I suspect that Storyline goes to quick to the next slide, so i changed the nextSlide trigger to waiting for a new variable to change: nextSlideAllowed. Default it is false, thus atm not going to the next slide. Im gonna see whether i can set that variable to true, thus going to the next page and whether all works then. - EmmanuelNdlecCommunity Member
Ok, thank's.
For the context, I'm trying to find a way to load a 4 dimensions array of values, because I am asked to create a kind of quiz that gives scores in 11 different topics.
There are 3 series of questions, each composed of 5 questions, for which there are 4 answers possible, and each answers affects differently 11 scores, all together.To help the person in charge of writing the questions and deciding wich answer gives how many points in wich score, I created a excel sheet that allows her to write down those points and check what score she gets when she selects what answers. She has to make many tries to precisely fine tune those points values.
In a classic Storyline approach, one would make 15 slides (3 * 5 questions), containg each 4 radio buttons, and therefore the number of triggers associated to them or to a "next" button in each slide would be 44 (4 answers * 11 values to change by answer), so that leads to 660 triggers (15 * 44).
There is no way I create and edit 660 different triggers extremely similar, the only difference betwwen them being an integer value.
So in the excel sheet I created a formula that writes all the values in a 4D js array syntax :
var myScores = [[[[ ........ ]]]];
organized as myScores[series][question][answer][points]This js array is declared in my modified version of your GlobalScript.js file, in which I also added a simple return score fucntion :
function getScore(serie,question,answer,score) {
console.log("score calling : ser=" + serie+ " , q=" + question+ " , a=" + answer+ " , sco=" + score);
return myScores[serie][question][answer][score];
}In Storyline, I set at the begining of each slide 2 variables (for declaring which serie number and question number are we playing), the answer number is set by 4 triggers associated to my radio buttons, and finally I use a javascript trigger on my next button that calls 11 times myGetScore function to set my 11 storyline score variables.
Doing so allows me to have exactly the same triggers in each slide, except for the starting one that declars which question of which serie are we in, but that I don't have to change whatever changes are made to the score matrix.
Each time my workmate will need to have a new version of the quiz with new point values, all I would have to do is edit the globalScript.js file in order to paste in it the result of the excel formula (lazy guy spotted, I agree), and republish after having re-set the webObjectURL value : 2 paste actions instead of 660 clic-edit is the kind of thing that makes my day :-D
Explaining all this and preparing my file in order to share it here lead me to better results : I do now see console messages from globalScript.js initialisation lines, but as soon as I call my getScore() function, nothing happens.