Alpine Data Binding3:10
Now here's the cool thing. If I add X data with an object here, we've immediately declared our container. So for example, now I could say within here, listen for a click of this button, and then just do an alert, just to give you an idea. Come back, refresh, click me, and you have your alert. This right here actually is my favorite thing about Alpine. There's almost no setup required. So for example, I could say message equals click me, and now I can bind to that data property like so. x-text equals message.
property like so. X text equals message. Now again, notice if you come from the view world, you might do something like V text. So the API, again, is very similar. So now if I give it a refresh, you still get the same thing. One last thing before we get going. Maybe when you click on the button, we will update that message. I have been clicked. All right. So we will default to click me for the message, but then when you click on the button, we
This is just a simple explanation with no code.
Rendering Card Grid4:50
So this would represent a single card. So what I could then do is iterate over all of the cards like this. $cards, $card, and then we'll have our div here. So now I could bind the style to something like background, and then the current color for that corresponding card. All right. Let's just bring back our height and give this a shot. So if we run this in the browser, we should see one green card, and we do. Okay. But it's green by default when it should really be facedown by default, right?
Okay. So now if I come back, it's set to gray because it hasn't been flipped. But if it is flipped, then it should be green, and this is what we want. Now what we could do, I'll show you a couple ways to handle this, but we could say when you click on the div, card.flipped equals true. Or if we want to toggle it, we could say, well, it's the opposite of what it currently is. So if it's not flipped, flip it. If it is flipped, unflip it, or just flip it back. You know what I mean?
want to do this all in line. So in many cases, using an inline object for your data is exactly what you want for simple dropdowns or modals or things like that. But otherwise, you can instead refer to a function that simply returns an object. So we can do that right down here to keep it simple. function game, and then just return an object. Okay. So I'm going to paste in what we had before. And now if we come back and refresh, we're still going to get the exact same thing. Okay.
And now if we come back and refresh, we're still going to get the exact same thing. Okay. Next, real quick, let's set the class here to cursor pointer, just to make it a little more clear that you can click this. Okay. So now it should be fairly easy to create however many cards we need, two, three, four. And I'm just going to duplicate it instead of cloning it. So how about green, red, blue, yellow, and yeah, again, I'm just duplicating this. But of course, if you want, you could do a deep copy here to remove the duplication. This is fine though.
Flip Logic and Matching7:53
also need to do a handful of things. At some point, for example, after the second card, we have to check, well, have you flipped two cards? If so, let's see if we have a match. So with that in mind, instead of doing the logic inline here, let's call a function like flipCard. And I will pass in the current card object. Okay. So now down here, I just add a method. flipCard, and we can console.log the card.
So now down here, I just add a method. flipCard, and we can console.log the card. All right. Let's bring up the inspection with command option C. I will click one. And sure enough, we have a proxy here, but you can see the original target. There's the object. Okay. So to return what we originally had, I could say card.flipped. So take the current value here and make it the opposite of what it currently is. Okay.
So take the current value here and make it the opposite of what it currently is. Okay. So this will reproduce what we had originally. Next we could say, well, if you have now flipped two cards, so it sounds like I need some way to check which cards have been flipped. And I could do something like this. If the length of the flippedCards is two, well, then we should check for a match, right? So let's say checkForMatch. But of course, if I run this, it's not going to work because flippedCards doesn't exist. So this sounds like a good use case for what is effectively a computed property.
But of course, if I run this, it's not going to work because flippedCards doesn't exist. So this sounds like a good use case for what is effectively a computed property. We can do that easily with a JavaScript getter. flippedCards. Now how will we figure out among this array, which of them have been flipped? Well, why don't we just filter it down? Filter this array down to only the objects where the flipped property is set to true. Like this. this.cards.filter(card => card.flipped === true). All right.
This.cards.filter the card, and I only want the cards where the flipped property is true. All right. So let's give this a shot. Refresh. One, two, and it's time to check for a match. Now we can check for a match like this. If the flipped cards, the first item there, and let's just, we don't have an identifier. Let's just use the color. That's fine. If the color equals the second flipped cards color, then once again, you have a match.
So at this point, we're not doing anything. So what do we do on the condition that we have a match? Well, it sounds like we need to set the cleared property to true. You have cleared those cards. So we could say this.flippedCards for each one. Let's set the cards cleared status to true. So now think about it. If we come back here, we have our div. Maybe we should only show this grid item if the card is not cleared. So let's negate it.
Maybe we should only show this grid item if the card is not cleared. So let's negate it. If the card has not been cleared yet, we want to show it. But if it has been cleared, it's been taken away. We don't need to see it anymore. So let's give that a shot. We run it. No match. So we don't do anything yet. But if we do it again with a match, they've now been cleared.
Adding Points Display11:50
Okay, so I think we have one benefit to this. Now we're only toggling the display of the button, not the grid item itself, which means when we have a match, the buttons will disappear, but we still have those grid containers. So the items won't reflow or move around is what I mean. All right, next, just for fun, let's store the points in the top right. So what I'll do is right up here at our game or our root div, let's add an <h1>, and I'm just going to fix it to the top right, and then maybe add a little padding around. Maybe we'll make the text bold, pretty large, and yeah, maybe that's fine for now. And then I'm going to set the text to your total number of points. So again, if I give this a run and look at the console, it'll let me know, points is
And then I'm going to set the text to your total number of points. So again, if I give this a run and look at the console, it'll let me know, points is not defined. Okay, let's add right here a new getter for the points because we'll have to calculate this. So how do we figure out how many points you have? Maybe we can just fetch up this array, give me only the ones that have been cleared, and then let's get the count. So you get one point per cleared card, right? So let's see.
So you get one point per cleared card, right? So let's see. We could do this again. I could say this.cards.filter the card, where the card.cleared is true, and then get the length. So I think that would work. If we come back and give it a refresh, we clear some cards, and that does update. But maybe we should make it clear what two is. So let's come back here, and maybe we'll have our span where that's set to the points, and then maybe another span that's a lot smaller, like extra small, and this will be PTS for
So let's just duplicate this, and we'll call this clearedCards, and the only difference is we're going to filter it down to only the cards that have been cleared. Okay, so now that we have a new computed property, I can update this to just return the length of the clearedCards. And it ends up being a little easier to understand, right? And it still works. Okay, but now if we continue on, those should have cleared, but it didn't work. So let's figure that out. If we scroll down, notice that after we determine if we have a match, and actually notice it took me a second there, and that's because it's not overly clear.
If we scroll down, notice that after we determine if we have a match, and actually notice it took me a second there, and that's because it's not overly clear. So what if we instead extracted this to a method like hasMatch? Let's do that now, hasMatch, and I'll paste that in. Okay, now it's a little more clear. If we have a match, then for each one, mark them as cleared. When you're done, though, we need to flip them back over. We are resetting it for the next round. So I could say this.flippedCards for each one. Let's just flip it back over.
So I could say this.flippedCards for each one. Let's just flip it back over. So again, notice how useful these computed properties or getters end up being. All right, let's give it a shot. We have a match, and let's do another one. And that works as well. Six points, eight points. Okay, but now we've cleared the board, but we didn't provide any feedback. There's our next step. After we have a match, let's see, is the game over?
Win Condition Check15:13
There's our next step. After we have a match, let's see, is the game over? And then by the way, we'll clean up some of this code in a moment. How would we check this? Well, here's an option. We could say, getTheClearedCards, and if the length of clearedCards equals the total length of cards total, then that means you've cleared all the cards. So we could say, alert, you won. Let's give it a shot. Match, match, match, match, and you get your alert.
Let's give it a shot. Match, match, match, match, and you get your alert. You won the game. However, it sounds like if I switch back, this is fine, but I sort of want to say if there are no remaining cards. That's really what I'm checking. So why don't we add a new getter, and we'll say remainingCards, and this is going to be similar to clearedCards, but we're just going to change it or negate it like so. So filter down the cards to only the ones that have not been cleared. Okay, so now I could say if there are no remaining cards.
Okay, what about the situation where there's no match? Well, you can see it does flip back over, but it does it so quickly that I never even get to see what this color is. It sounds like we need to wait for just a moment, and I'll show you how to do that. So if the flipCards length is two, then check for a match. But yeah, notice if there is no match, it instantly flips back over. So we could do something like this, setTimeout, and then how long should we wait? Maybe a second. So give the user just a second to see what they clicked before you reset. There you go.
That's kind of the basic idea of what's going on. So I could say, yes, set a timeout and resolve that timeout after the number of milliseconds you provide. So we could do that, or let's see if I can convert this to an error function. There we go. This is equivalent. OK, so now we're not returning a setTimeout. We are returning a promise to let you know when it's time to continue. So await for that promise to resolve, and only then should we continue forward. Now I should be able to do the exact same thing here.
Flash Message Component22:07
Let's just use margin bottom. Something like that, maybe rounded. You get the idea, a simple flash message. So now notice, because we have a new Alpine data container here, we could say, well, show it if this property is set to true. At the moment, it's set to false, so we don't see it. But if we could toggle it to true, we can see it. So now, let's do a couple of things. We'll update the text to the message, and I'm just going to set this to defaultMessage, so you see something for now, but then we will remove that.
We create our event. We're going to call it flash. And all we need to provide is the details associated with this event. In our case, we'll set the message to hello, something like that. And yeah, that's your event. Now we only need to dispatch it on the window. So when we run this, we should get an alert. Oh, I'm sorry, we called it event. Anyways, we fire that event. Our Alpine component listens for that event, and then it responds to that event.
The detail, again, will be... We can use the shorthand. So I could do this, but because they're the same with ES6, I can just use the property alone. Okay, so now we're going to run it again, but this time I'm not going to alert cot. I want to alert the message you provide. Now I can grab that by getting the event. This is a magic variable that will be available to you. And we're going to look at the detail prop. So event.detail.message. All right, let's give that one a shot.
So event.detail.message. All right, let's give that one a shot. So we run it again. I'm going to call our new global flash helper. We run it. It catches it, and it alerts that message. We can do it again. Kind of nifty. So I think we have everything we need at this point. The only remaining step is, of course, I don't want to alert.
So I think we have everything we need at this point. The only remaining step is, of course, I don't want to alert. I want to update our message property equal to that message itself. The only remaining step is to show the flash message. So it defaults to false. Listen for a new flash message to come in, update the message, and then show it. So one more time, we flash something, and it now shows. Now we could fade this out or simply add a close button. So why don't we do that here? So once again, we have our message here.
So why don't we do that here? So once again, we have our message here. So let's get rid of it up there. And then we'll have a little button here with an X symbol. But I can do times to make that a little cleaner. Now when you click on that button, hide it. show equals false. All right, let's see what this looks like. One more time, we flash our message, and there's your X. I click on it, and it hides.
