FREELessons: 7Length: 56 minutes

Next lesson playing in 5 seconds

Cancel
  • Overview
  • Transcript

2.5 Working With State

Stimulus doesn't track state and automagically update the DOM. Instead, we track state only when we need to, and we store it in the DOM using data- attributes. I'll introduce you to the Stimulus data API in this lesson.

2.5 Working With State

State is a very important part of any application. It does not matter what language or platform, it doesn't matter if it's a client side a server side or even just a conventional application. It doesn't matter, you will at some point need to work with state. Now you might be thinking but, wait a second, didn't he say something about stimulus not tracking state? And yes, that is indeed true. Stimulus does not track state, it is something that we have to track on our own. And that's okay, because we're not using state for a single page application. That's where state gets rather tricky. In our particular case, state is going to be very simple. Because we are writing just a traditional web application. So we're going to start by creating a new file inside of our public folder. Although that didn't quite work out here, let's move that up to public. It's going to be an html file, you can call it whatever you want, but I'm gonna call it info.html because we're going to build an info panel. We will have two columns and the left hand side is going to be our quote unquote sidebar, with links that we would click on. Then on the right hand side it's going to have our content. So it's going to be kind of like a tab panel, except that we are going to write the functionality ourselves as opposed to using Bootstrap to do it, all though we will be using the CSS from Bootstrap. We will not be using any of the jQuery components. Now with pulling in Bootstrap, we could have done that with npm, but in this particular case it's just easier to use the cdm. Now, we do need to reference our app.js file. So let's go ahead and add to that so that we have a reference there. And then we can get started writing our markup, because that's really where we need to get started with our application. So we're going to have a div that has a class of container. And let's go ahead and assign our controller here as well. So let's call this infopanel, because that's kind of what it's going to be. And then we will have a row that is going to have two columns on the left hand side is going to be our sidebar. So as far as the class is concerned let's use the column small 2 and then for the right hand side, for our content let's use the col mid. So we have our sidebar here, then we have our content. So as far as the markup for our sidebar, we're going to create a list group. But it's not going to use a UL and then list items, instead we're going to have a div element that has the class of list group. And then we will have multiple a elements, that will have the class list group item, but they also need list group item action. And since these are going to be targets, because we need to be able to access these things within our controller. We are going to go ahead and set the data-target attribute. As far as the name is concerned, we can call them infopanel.item because these are the items that we are going to click. Now if you'll notice, I'm not changing the name of data-targets, that gives three of these. And that is because, even though these have the same targets name, the controller is going to handle that just fine. Instead of using the item target property that we would normally have, we would have a different property that is going to be an array of all of these elements. And we will look at that here in a moment. So as far as the text, let's have Item 1, and then Item 2, and Item 3. Let's also set the href, so that we have something there and it's simply going to be a #. And then as far as our content is concerned, we're going to use the tab-content class. And then inside we are going to have individual tab panes, and we will have three of those because we have three items. So as far as the mark up, we will have the class of tab-pane, let's give these a role of tab panel, and then let's also set the data-target attribute as well. Because these are going to be targets that we will work with inside of our controller. And as far as the name is concerned, we'll call them simply content. Now, once again I'm going to leave the name alone. We will work with the different property name that's going to refer to all of these as items within an array and everything is just going to work. Now as far as the content, let's say content for item 1, and then we can do the same thing for the others. So we have a starting point, let's take a break from our mark-up and let's create a new controller. And since we called that controller info panel, this will be infopanel_controller.js. And let's go ahead and import controller, because we're going to need that pretty much immediately. This is of course from stimulus. And then we want to export that class that extends controller, and there we go. So, we have two targets that we're going to work with, so let's go ahead and have our target's property, it's going to return an array where we have item and we have content. Even though we have multiple items and multiple contents, we still use the singular form of that because that is the exact name that we have inside of our markup. And now we need to do something whenever we click on our item targets. So let's have a method called show that we will set up for our action. So let's go back to our markup, and somewhere around here let's just add the data-action. In this case we're going to say click, and then infopanel#show, and then we will add those to the other items. So we will have our markup there pretty much complete, we will come back here, but for now we can spend our time in JavaScript. Okay, so whenever we click on our items the first thing we want to do is prevent the default action of clicking on a link. Of course, we don't want to actually navigate so we're going to do that. And since we have multiple items and multiple contents, instead of using itemTarget and contentTarget, instead we just have a plural. We have contentTargets or itemTargets. This is going to be the property that has an array of all of the targets that have that particular name. So contentTargets is going to contain all of our content, the item targets is going to contain all of our the items. So in order to activate one of these, we are going to use the class of active and then when it comes to the content, we're going to have the class of show and active. And in fact, all of these need fade as well. So let's go ahead and add that. And so, as we click on individual items, we're going to have to deactivate whichever is currently active, and then activate the other content. So, in some way we need to track which item is active and which item isn't active. And yes, we could query the dom for that particular information. However, it would be a lot easier if we just like stored the index, because we have three items, we have three pieces of content. So the index makes sense, if the item with an index of zero, the first item, is active, then the first content is active. If the item with an index of two is active, then the content with an index of two is active. So if we keep track of just the index that is currently active, then everything will be fine. So the question then is, how do we keep track of this? And the solution is to store it in the DOM. With stimulus that is essentially how we maintain state. We store all of our data in the DOM because, well, that just makes sense, we're storing everything else in the DOM. What we can do is where we have defined our controller, we could also add a data attribute here. We can say data, and then we could just say index, if we wanted to, and we can initialize that as 0. So the beginning state should already be in the document, just like I have done here. I added active for the first item, I added show and active for the first content. And then data-index is of course 0. So let's go back to our controller, and let's just write that code. So let's get our current index, and we will do that with this getAttribute, and then data-index. Now there's actually a better way of getting this information, and we will talk about that in a few moments. But for now we are going to get this working and then we will come back, and make the necessary changes. Now we want this as an integer, so let's go ahead and parse it as an int. And then we're simply going to loop over all of our itemTargets. So we'll use the forEach, and we want the item, and we also want the index, because at some point we have to get the index of the item that we are wanting to activate. So, what we could do is inside of this loop we could just check to see if the current item is equal to the target that we clicked on, and if they are equal then we will activate whatever that item is. And we can do that based upon the index because we're doing everything else based upon the index, that is the data that we are showing. So before we activate that however, we need to deactivate the currentIndex, so we would parse currentIndex there, so that we would deactivate then we would activate. So we'd just need to write those methods. Let's start with deactivate, and we're going to get the index there. And all we need to do is use our itemTargets, we'll specify the index, we'll use the classList, and we will simply remove the active class. And we will essentially do the same thing but for the contentTargets, but we also need to remove the show class as well. So we will be removing two CSS classes there. So that is the deactivate method. Then we just have the activate, and it is essentially going to reverse whatever the deactivate does. So we can take that code and use it as a basis, because instead of remove, we'll just call add. And there we go, but we also need to track this currentIndex now. So we can say this setAttribute and then data-index and then the value would be the index that was provided. So, let run this and let's see if it works. Let's call our npm run dev, so that that will kick off webPack, will also kick off our lite-server. We don't get any errors, which is good. So now let's go to the browser, and let's go to info.html. And whenever we click on any one of these items, well nothing happens, we at least don't navigate so lets see if we get any errors in the console and we do. Now we see first of all that this getAttribute is not a function. What? Well that kind of makes sense that was error on my part because, whenever we refer to this we are referring to the controller object, which is not the element that we are currently working with. We can however, access that element by simply saying element. So we have a property called element, which refers to the element that is our controller. And in that case, this is the div element. So, wherever we have getAttribute, we could say this .elementgetAttribute and then whenever we set that we could say this .element.setAttribute. Unfortunately this means we have to rebuild our code, and we will do that, we will run lite-server once again. And now, let's see if this works. So let's refresh the page, we click on it and we see that the content changes, the active item changes, and everything works. Now I mentioned that there is a better way of working with the data here and there is. Instead of calling this data-index, we could call this data-infopanel-index. Now the use of infopanel is important here, because this is going to allow this attribute to be added to the data API. So we have to use our controller name here, data-controllername-whatever we want this particular name or value to be called, and index makes a lot of sense. So instead of using just normal DOM methods we could say this.data.get, and then all we have to specify is the simple name. So we have this data API that we can use, we can get a value just like we did there, we can set a value just like this, this data.set. Once again, we can say the simple name index, we don't say data-infopanel-index. And there's also a has method, so if you wanted to test to see if there was an attribute called data-infopanel-index, you could use the has method there. But once again, this is a lot simpler. So let’s rebuild our code and then we will see it work exactly as it did before but now we're using the Data API given to us by Stimulus. So once again let’s refresh the page, we click on the individual items, the content changes, the active item and everything works just as it should. So when it comes to storing states, with stimulus we use the DOM. Stimulus gives us the Data API to easily set, get, and to check if we have particular data for our controller.

Back to the top