Forum Discussion
Changing state directly from JavaScript?
Hello Heroes!
Maybe someone out there can help me out! I've been playing with JavaScript and Storyline, you can see an example here: http://bit.ly/1IjNEKs.
But, what I can't figure out is how to access an object directly from JavaScript. I would like to change the state of a graphic from JavaScript.
In the example above, if you launch the course with adding ?test=Confused at the end of the URL link, and go to Chapter 3, the character will show the confused state. But essentially, I'm changing a variable first, which leads to a trigger to change the state. If I want ten different states, that's ten more manual triggers.
What I'd like to do is change the character's state directly. Any thoughts?
- SharonGozaCommunity Member
Hmm, can those images be used as buttons and also have a hover state?
First thing I tried was this:
The button starts out on normal. If the user answers correctly, it changes to the green and incorrectly to the red. I tried to manage hover states using the hover trigger and changing the state to either darkstate, darkgood, or darkbad. However, on moving the mouse off of the button, the button would sometimes completely disappear as if the forced "Restore previous state when the user hovers out" was getting confused.
My second implementation put each button on a separate layer with their respective triggers, but still had the same disappearing issues.
My third implementation split all of those into 3 separate buttons which I then hid or unhid, however the logic behind that using triggers causes things to be checked every time the timeline starts and since I have 10 of these buttons, that has been resulting in a slow down of the page load time but did fix the disappearing buttons.
- MathNotermans-9Community Member
Yes thats possible. I make a sample tomorrow ??
- MathNotermans-9Community Member
First steps done. So in the course attached images on screen are hidden and shown by Javascript.
Main trick behind is is using the 'accessibility-names'. When you check the title you notice the image-buttons are named 'state hatch bad' etc, as you can see in this image.
For ease of use in coding Javascript i always rename the layer to its accessibility name. Thus making it easy to find and remember naming. The 'space' in the naming makes it easy to select all elements...by using a selector like this...
$("[data-acc-text*='state']"
or a specific subselection of the elements...$("[data-acc-text*='state dark']"
or even a specific element$("[data-acc-text*='state dark good']"
The sample as added uses a Webwindow with specific Javascript libraries to ensure all works. I always use JQuery and GSAP Tweenmax. As is GSAP is not free, when you want to use GSAP animation on a course or site, you need a licence. Its totally worth it for sure.
This for sure not yet is a solution for how to script 'states' with images. Its only a first step. Next step will be replacing an images source. Then its quite a bit closer to the way Storyline handles its states...That will be next step i show.
- MathNotermans-9Community Member
Diving a bit deeper into the 'states' of Storyline gave me some more insights and i can control a state now by Javascript. At least when its purely an image...if you use Storyline filters on images...that has to be emulated by script then too.
Check out this sample.States showing/hiding with Javascript
Its an element with 2 states. The top 2 buttons 'Set State1 SL' and 'Set State2 SL' do just that...standard Storyline actions that enable a state.
After some digging into Storyline i noticed that for states the DIV is just toggled. So set to visible or hidden. With Javascript i can trigger visible DIVS, but when one is hidden i cannot. Thus the button getElements. Clicking that button makes both DIVS visible and then the buttons set State JS work...toggling visibility on the 2 states.
Gonna finetune that with Sharon's sample...- ElainePowellCommunity Member
That's awesome. Editing those states could be the answer to so many development problems.
So each state is a toggle? Like NormalState0 & NormalState1 for Normal?
When you can, please share your javascript code for these. I have an idea accessing these states might be a big deal.
I didn't read the whole comment thread. I've got your code.
Thank you so much!
- ElainePowellCommunity Member
I've gone through the code - with jQuery and GSAP it's more complicated than I can parse.
Would you mind helping me with the Vanilla Javascript?
- MathNotermans-9Community Member
After some more digging into Storyline's states-approach i discovered that SL handles states different depending on the amount of images used. As seen in a post above for only 2 images it creates 2 hidden Divs and toggles those. If it would handle it the same for multiple images, my approach above would work and then you could control any amount of states. But it handles multiple images differently. When more then 1 image there is only 1 SVG and the source image for that SVG is replaced by an png/jpg when switching states. Thus having multiple scenarios for states makes it really hard to script that consistently with Javascript.
Thus said i switched the approach, and found a well working solution.
Enter: the sprite and clippath approach...
In this sample below there is only 1 image... a large png, visible at start. When you click any of the state buttons the state is scripted. What happens is that with Javascripted the DIV gets masked with a clippath and then the complete DIV gets moved so it stays more or less in position. Ideally i would use background-position to move the image but it doesnot work ( yet )For now this is a nice solution to get a state-like setup that you can script with Javascript.
As always i use JQuery and GSAP, all of this you can do in Vanilla Javascript, but i like the ease of use of GSAP for this.
- SharonGozaCommunity Member
Thanks, Math. I'm going to take a look at your code and see if it'll help or give me a starting point.
- MathNotermans-9Community Member
Although Tweenlite still works in Storyline...use gsap. instead of Tweenlite. because Articulate updated its internal libraries a while ago.
- MathNotermans-9Community Member
No problem Michael. A few errors in your code. You do have to register a GSAP plugin for it to work.
gsap.registerPlugin(Draggable);
And the loading part of your Javascript had some errors. 'elseif' needs to be 'else if'.. and some other errors i couldnot find, so i copied a script i had thats working.
Also it also works untill the second click on the element...not sure why... ill make a perfect sample soon.Kind regards,
Math - MathNotermans-9Community Member
Figured out why the drag-drop behaviour was so weird. You create a Draggable not on a click of an element...but at start of the timeline. So best spot is when the javascript is loaded. Then it works fine. However still a lot to do to get a perfect drag-drop because you need to watch dragEnd and dropEnd... and all kind of different conditions. I am making a simple sample to show.
In this sample here i fixed the basics. - SharonGoza-f625Community Member
I'm back finding that I'd really like to do this again. I'd love to have something like:
player.SetState("nameofobject", "state name");
- MathNotermans-9Community Member
As Articulate updates HTML and changes structure of it all also without warning to developers upfront old code especially when targeting existing HTML structure often fails. Then you have to figure out what change Articulate did and fix your code accordingly. So im checking now whether states are as before...or maybe something changed and they might be easier accessible.
When checking a simple SL with states in Firefox you will notice a state basically is an invisible DIV as before. When switching to it using a trigger...it is shown and the original state is hidden.
Basically nothing has changed on this thus.
It is possible to find all DIVs related to an element ( say it has statenames: 'Over', 'Down', 'Custom1', 'Custom2' ) and then target the element...hide all states and show the one you want to show with javascript.
Im gonna see if i can get that working...
First tests show that this is ( more or less ) the structure any image with states has.The main element:
.slide-object-stategroup.shown;A div with class of .group in it
Then the first basic image that is shown has this class
( all images in SL get converted to inline SVG )
div.slide-object-vectorshape.shown;
div.slide-object-vectorshape:nth-child(1);This is my custom state i added. A hidden DIV below it.
div.slide-object-vectorshape.hidden;
div.slide-object-vectorshape:nth-child(1);
Next step is trying to use this structure to get all divs in a specified state-group.
If i can get those ( and im quite sure i can ) then i can hide all, and show any of the states at will using javascript.- MathNotermans-9Community Member
Oh my, this really remembers me what i dislike in Storyline development. The HTML generated when adding images manually too Storyline differs to when in 'Edit State-mode' you either use 'Duplicate State' and then change the state by importing another image.. or when you use 'Add Pictures as States'.
For now i use ' Duplicate State' and change the image. That seems workable. The other approaches on creating new States give complete other HTML structure. But this really is a Articulate/Storyline thing. As they want to keep it backwards compatible with older versions ( at least as much as possible ) they tend to inherit weird behaviour from old versions too.
- MathNotermans-9Community Member
First steps working properly. Based upon duplicating states for new states. As mentioned in my earlier posts... Storyline makes different HTML when creating the states differently. I do think thats really bad coding...but...it is what it is...
The code below gets an specific element and gets all divs in in it with an data-model-id attribute, thus only the states.let baseElement = document.querySelector("[data-acc-text='myElement']");
let parent = baseElement.parentNode;
let allDIVS = parent.getElementsByTagName("div");
for (let i = 0; i < allDIVS.length; i++) {
let actualClassName = allDIVS[i].className;
let dataModelID = allDIVS[i].getAttribute('data-model-id');
console.log(i+" : "+actualClassName+" | "+dataModelID);
if(dataModelID != null ){
console.log(i+" dataModelID: "+dataModelID);
}
}
Now you can hide all of them...and show any of them.
Thats for a next one :-)
PS: the statename as is in Storyline is nowhere referenced in the HTML. - MathNotermans-9Community Member
Allthough i do have some hickups in showing the proper state...
this code hides all states of a given element.let baseElement = document.querySelector("[data-acc-text='myElement']");
let parent = baseElement.parentNode;
let allDIVS = parent.getElementsByTagName("div");
hideAllStates();
function hideAllStates(){
for (let i = 0; i < allDIVS.length; i++) {
let dataModelID = allDIVS[i].getAttribute('data-model-id');
if(dataModelID != null ){
allDIVS[i].classList.remove('shown');
allDIVS[i].classList.add('hidden');
}
}
}
Then you can target a state and show that.let baseElement = document.querySelector("[data-acc-text='myElement']");
let parent = baseElement.parentNode;
let allDIVS = parent.getElementsByTagName("div");
showState();
function showState(){
for (let i = 0; i < allDIVS.length; i++) {
let dataModelID = allDIVS[i].getAttribute('data-model-id');
if(dataModelID != null ){
allDIVS[3].classList.remove('hidden');
allDIVS[3].classList.add('shown');
}
}
}
As said this only works when duplicating states. If you make them any other way, Storyline makes up other HTML and this wont work. Also there still are some things to fix in the showing part... but basically it works.
- NedimCommunity Member
Huh...as Lectora would say: "buttonObject.setState('customState');"