Using JavaScript with True/False Buttons

Jan 20, 2022

Hello! I am developing a menu selection course for students and I need assistance.  

Students will choose items to build their meal. Each item has a button associated with it. Each button is associated with (6) variables-- calories, fats, proteins, carbs/sugars, True/False, Text. When students 'checkout' they will be brought to a results slide which will breakdown the nutritional value of their 'meal' and list their selections.

On my results slide, all of the nutritional values of the selected items appear. I was also able to use JavaScript that I adapted from a previous storyline discussion thread to compile my list of selected items on the result slide:

var player = GetPlayer();
var List_Smash = player.GetVar("List_Smash");
var Bacon = player.GetVar("Bacon");
var Concate = List_Smash + Bacon;
player.SetVar("List_Smash",Concate);

For each item to appear in list format, I have the following triggers:

Trigger #1:  Set ItemT/FVariable to value True When the state of ItemButton is selected.

Trigger #2: Execute JavaScript (example above) When user clicks ItemButton, if ItemT/FVariable = value True

My only issue is, every time the associated button of an item is selected, SL adds the item to a list. I am unsure what I have to do in order to make sure only selected items are added to the list.

I am hoping this is possible! Thank you in advance for your assistance!

23 Replies
Walt Hamilton

My only issue is, every time the associated button of an item is selected, SL adds the item to a list. I am unsure what I have to do in order to make sure only selected items are added to the list.

As I read this, it says, "Selected items are added. How do I add selected items?"

Maybe you could re-write your question to give it more clarity. Better yet, attach your .story file here, and explain what it does that is different than what you want it to do. See if that gets you some help.

Math Notermans

Although as Walt says its hard to figure out exactly whats wrong without a sample... i do have 2 suggestions that might help.

Remove the Trigger#2 from the list and add it to a 'When variable changes'. That way you can only trigger it a wanted change of some variable.

Also be aware that variables in Storyline are always strings. So i do think the line in your code...
var Concate = List_Smash + Bacon;
is a bit weird. As i see it both List_Smash and Bacon are 2 text variables and all you do is adding them together. In fact the same as concat( ) does in Javascript but it would be good to use descriptive names as that makes life easier. So something like..
var breakfastString = List_Smash + Bacon;
would make more sense to me. You can always use this to detect what type a value is...
console.log("List_Smash is: "+typeof List_Smash);

But to really help you...add the Storyline.

Christine Sawh

Hello Walt and Math:

Thank you both for review.  I did not proofread my post and can see how it was unclear.  I have attached by Storyline file for review.

Essentially, when an item is picked, I would like it to appear in list form on a 'results' slide along with the item's nutritional values so that students can see their selections and proceed with questions following the exercise.

I've placed a table on my 'dessert' slide for the purposes of this post. In the example below, I have selected chocolate milkshake, fruit cup, and chocolate chip cookie.  These items appear in the table along with their nutritional counts.  This is exactly what I want to happen.

For each item, I have a combination of triggers and variables associated to a object checkbox (e.g. ChocChip).

Variables:

Triggers:

One Javascript trigger for the listing of items:

var player = GetPlayer();
var List_Dessert = player.GetVar("List_Dessert");
var ChocChip = player.GetVar("ChocChip");
var ChocChip_Button = player.GetVar("ChocChip_Button");
var Concate = List_Dessert + ChocChip;
player.SetVar("List_Dessert",Concate);

4 Javascript triggers for the listing of each nutritional value. Calorie listing example below:

var player = GetPlayer();
var List_Dessert_Calories = player.GetVar("List_Dessert_Calories");
var unitPrice = player.GetVar("ChocChip_Cal");
unitPrice = unitPrice.toFixed(0)
var displayPrice = unitPrice + "<br />";
var Concate = List_Dessert_Calories + displayPrice;
player.SetVar("List_Dessert_Calories",Concate);

The problem I am facing is, when items are unselected, they are added again to the table instead of being removed or not showing up. Here is an example below.

I don't know what the issue is so, your assistance is appreciated!

Lastly, referencing Math's comment:

Also be aware that variables in Storyline are always strings. So i do think the line in your code...
var Concate = List_Smash + Bacon;
is a bit weird. As i see it both List_Smash and Bacon are 2 text variables and all you do is adding them together. In fact the same as concat( ) does in Javascript but it would be good to use descriptive names as that makes life easier. So something like..
var breakfastString = List_Smash + Bacon;
would make more sense to me. You can always use this to detect what type a value is...
console.log("List_Smash is: "+typeof List_Smash);

I adapted all of my JS codes from this post:  https://community.articulate.com/discussions/building-better-courses/building-a-text-list-using-variables-9076700d-32e3-4351-b09c-0a333ae871cf.  So, if there is a better way of typing the JS command, please let me know.

Thank you both!

~ Christine

Math Notermans

Thats quite a nice explanation Christine ;-) Out of the blue i guess the value isnot deleted from wherever its stored when deselecting it. If that was the case...whenever reselecting it again...it wouldnot matter it was added again... in fact you want that. Tomorrow morning im gonna check your file.

Walt Hamilton

In short , the problem is that every time the learner clicks an item, the javascript runs and adds it to the list. The solution is that you can get a more accurate number by counting at the end, instead of trying to constantly adjust the variable up and down as things change, and you only have to do it once. Trying to adjust on the fly is difficult, fraught with the possibility for errors and duplicates, and very complicated if the learner changes their mind.

If it were mine, here's what I'd do:

1. Create a Calculate layer for each restaurant, and one for dessert and one for drinks.

2. Don't attach any triggers to the food items. Create a trigger for Dessert  to set NextSlide to "dessert" when Dessert is clicked, and one to set NextSlide to :"drinks" when Beverages is clicked

3. On each slide (each restaurant, dessert, and drinks) have one trigger that shows the Calculate layer when the learner is ready to move on. (Clicks Dessert, Drinks, or checkout.)

4. Set those layers to have .5 sec timeline, Not hide other layers, Hide themself when the timeline ends.

5. On the Calculate layer, create triggers to set all the variables to 0 (or false) when the timeline starts.

6. Create triggers to set the variables to appropriate values when the timeline on the layer reaches .2 if the state of the button is selected. This will work even if a layer is showing and the buttons are on the base, as long as you haven't created any button sets. For the number variables, use the trigger to add, for example, FlourTortillaCalories to TotalCalories if the Flour Tortilla button is selected, etc. Once you have the numbers all added up, leave them alone. For the T/F variables, set, for example, FlourTortilla to True if the FlourTortilla button is selected.

7. Create a trigger to hide the Calculate layer when the timeline ends on the Calculate layer.

8.Create a trigger to jump to the Desserts if NextSlide = "dessert", and one to jump to Beverages if NextSlide = "drinks" when the timeline ends on the layer.

9. On the Results slide, instead of List_Smash, List_Chipotle, and List_Panera, I would have 1 variable : List_Food, and the same for Calories, Carbs, Fats, and Proteins

10. Consolidate all the javascripts into one script that is executed when the timeline starts on the Results slide. Get the player once, then set all the text variables, concatenating the appropriate text if the corresponding variable is True. The numeric variables are already set.

Once the learner is ready to move from a slide, the layer calculates which items are selected, and sets the corresponding variables. When they checkout, the javascript builds the lists, just like it is doing now. No worries about when what happens, or, more importantly, no possibility of duplicating items.

It appears that the javascript is probably working correctly, if the right information is passed to it, and it only runs once. Still, Math will probably find some ways to streamline it.

Any questions, ask.

Math Notermans

As i mentioned in my out-of-the-blue guess.... the value is never deleted...further more there is only a check on when a button 'is selected'. As you add the values selected to a Storyline text variable ( i create Arrays or JSON Objects in cases like this, making it much easier to sort, select, remove or do whatever is needed with it ) its a bit tougher to find and delete the parts of it when deselecting . 
Tried fixing it in your sample...but that didnot work ( yet ). Having trouble deleting parts of your Storyline variable/string. So im gonna remake part of it so all values get pushed into arrays. Then its easy to find and delete an array-entry. Have to focus on other things now, but am coming back to you with a solution...

Math Notermans

Working approach using Arrays.
https://360.articulate.com/review/content/7d62d517-ca94-4703-af3b-2e10dbcc8340/review

Using arrays its not that difficult because you can use all built-in functionality of arrays quite easily. Mainly there are a few functions in the script. First of all i got rid of the <br /> in your variables as that made it tough to convert the Storyline variables to a array.

Core part of the script is this...
if(findEntryIndex(listArray,newValue)== -1){
listArray.push(newValue);
}else{
removeEntry(listArray,findEntryIndex(listArray,newValue));
}
Here i check whether the value selected already is in the array. If it is it returns a index to remove... if not it pushes the value into the array.

Hope this helps to get it working.

Kind regards,
Math

Phil Mayor

Looks like this was built form a sample a posted a long time ago, sorry this was not an elegant build as I just kept building on top of what I had rather than rewriting the whole thing (which I should have done).

Maths solution is a good elegant solution, if you wanted to fix what you have I would have added the triggers to a layer and added a trigger to reset concate variable and show the layer each time you click a checkbox, nowhere near as elegant as Math's solution!

Walt Hamilton

Phil is right, that Math has a more elegant solution (as we knew he would),  Still, philosophically, while both solve the problem of duplicating entries, I would favor Phil's answer because more of it can be done in SL. My rule of thumb about javascript is to use it only as a last resort, and as sparingly as possible. That is based solely on the thought that in the future, if this project needs to be modified or maintained, there may not be a programmer available.

Understand that this particular problem cannot be solved without some js, but my counsel is the less, the better.

My answer above steps you through the same solution that Phil is proposing.

Christine Sawh

Status Update: Feeling accomplished and defeated.  I fear that my 1st build in SL is a lot bigger than I anticipated.

Math's arrays worked perfectly for the names of the selected items. However, along with the name of the items, I would like the nutritional values of the selected items to also be listed. This is where I ran into trouble.

In the example below, Soda and Lemonade both have 0 protein and 0 fats. Instead of these numbers showing up in both columns in list form, they are hidden.  I know its because this section of Math's script:

if(findEntryIndex(listArray,newValue)== -1){
listArray.push(newValue);
}else{
removeEntry(listArray,findEntryIndex(listArray,newValue));
}

Is there a way that I can adjust Math's array to list the duplicate values of selected items?

If not, I think the next step may be for me to do as Phil shared:

...if you wanted to fix what you have I would have added the triggers to a layer and added a trigger to reset concate variable and show the layer each time you click a checkbox...

I am just unsure how to go about this since I have (4) variable for each food item. 

Walt, thank you for the outline! If there isn't a fix for me at this point, I'll rebuild this whole course per your direction and see where I end up.

Thank you again in advance!

~ Christine

Math Notermans

Im not 100% sure what your problem is Christine, but i do think its as easy as creating separate arrays for the nutritional values... eg. create a list/array for Proteins and push/remove/read the values from there....

Quickly added it as sample here...

/*
So now we need to find and delete the selected value from the Storyline variable
*/
var player = GetPlayer();
var listArray=[];
var proteinArray= [];
var StorylineList = player.GetVar("listArray");
var StorylineProteinList = player.GetVar("proteinListArray");
var newValue = player.GetVar("newValue");
var newProteinValue = player.GetVar("newProteinValue");

if(StorylineList!=""){
listArray = StorylineList.split(",");
}
if(StorylineProteinList!=""){
proteinArray = StorylineProteinList.split(",");
}
if(findEntryIndex(listArray,newValue)== -1){
listArray.push(newValue);
}else{
removeEntry(listArray,findEntryIndex(listArray,newValue));
}

if(findEntryIndex(proteinArray,newProteinValue)== -1){
proteinArray.push(newProteinValue);
}else{
removeEntry(proteinArray,findEntryIndex(proteinArray,newProteinValue));
}

var cleanArray = uniq(listArray);
var cleanProteinArray = uniq(proteinArray);
player.SetVar("proteinListArray",cleanProteinArray.toString());
player.SetVar("listArray",cleanArray.toString());
player.SetVar("displayList",cleanArray.join("<br />"));
player.SetVar("proteinDisplayList",cleanProteinArray.join("<br />"));

function uniq(a) {
return Array.from(new Set(a));
}

function findEntryIndex(_array,_string){
return _array.indexOf(_string);
}

function removeEntry(_array,_index){
return _array.splice(_index,1);
}

 

Do use a proper Javascript enabled texteditor. Although Storyline's editor is improved now, it still is quite inferior to editors like Sublime Text and Brackets.

 

Christine Sawh

Hello Math:

This is my 1st experience building in Storyline so, I will definitely check out the other editors you referenced.

I used the array you provided for the item names-- it worked perfectly.

I then used the array you provided for the nutritional values and ran into trouble. The array removes duplicates and I don't need this feature when it comes to the nutritional values. For example:

Water = 0 cal, 0 protein, 0 fat, 0 carbs

Soda = 245 cal, 0 protein, 0 fat, 66 carbs

Lemonade = 260 cal, 0 protein, 0 fat, 70 carbs

All of these have the same values for protein and fat so, I would like these values listed 3 times in the protein and fat columns when the items are selected.

Currently, if these items are selected together, then, protein and fat columns do not have '0' listed 3 times, its left blank. 

I'm unsure if there is a work around for this.  Hope this makes sense!

~ Christine

 

Math Notermans

Although this works..
https://360.articulate.com/review/content/5a979e06-302f-4ba4-9c7f-dcd53e8af038/review

Im not completly happy with it... as the alignment of the values of the ingredients is tough to get right... as its pure text.

I wanted to use a multidimensional Array, but for a quick solution i used a simple array and pass along the index ( arrays are always zero-based ) from Storyline.
If this works for you its good...if you too don't like the alignment and rather have more textboxes so you can finetune alignment in Storyline... i fix it when i got some time...

Christine Sawh

Hello Math!

Thank you again for your assistance. I adapted your code for all of my slides and then noticed something odd. So, I checked out your linked file and noticed it as well.

 If I choose any items out of order, the 1st item in the list that shows us is 'Chocolate Milkshake' as it is the 1st item in the ListArray and so forth . Here are 2 examples below:

Do you have a suggestion?

Thank you again for your assistance!

~ Christine

Math Notermans

So got that fixed...
https://360.articulate.com/review/content/5a979e06-302f-4ba4-9c7f-dcd53e8af038/review

Basically it was getting the wrong index from the array... it should use the value returned from the click on the ingredients.. Well thats fixed inhere.

Im now gonna add different textfields and arrays for calories, protein, fat and carbs to make it better alignable.

Math Notermans

And here that version is. Splitted up the array into separate ones for calories, protein...etc. and using those now.
https://360.articulate.com/review/content/923fee11-6b92-4251-85a1-259c4462382a/review

One thing to watch with this approach is the length of your items name / textfield. If its too short it will wrap to a next line...and when that doesnot happen with your nutrition values...well then it rapidly becomes hard to decipher for your student.

Have a nice day,
Math

Christine Sawh

Math:

I just incorporated your array into my SL and finalized it for review; it worked for all 52 items! I am indebted to you for all of your assistance and cannot convey how thankful I am. 

Everyone's tips provided a lot of education and demonstrated the multiple ways I could build future courses. For this reason, I look forward to learning and creating more in SL.

Thanks again!

~ Christine