Forum Discussion
Using JavaScript with True/False Buttons
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
- WaltHamiltonSuper Hero
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.
- MathNotermans-9Community Member
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. - ChristineSawhCommunity Member
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
- MathNotermans-9Community Member
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.
- ChristineSawhCommunity Member
Thanks in advance Math! I've been playing around with this for DAYS and learning soo much so, I cannot wait to find out what the issue could be.
- WaltHamiltonSuper Hero
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.
- MathNotermans-9Community Member
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... - MathNotermans-9Community Member
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 - PhilMayorSuper Hero
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!
- WaltHamiltonSuper Hero
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.
- PhilMayorSuper Hero
Sorry Walt didn’t see you had proposed the same solution. I like both ways, I lean towards Math’s because the original build from me was a mess!
Sent from my iPhone
- ChristineSawhCommunity Member
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
- MathNotermans-9Community Member
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.