Article show route0:00
Now that all of our tests are passing, I want to switch over to the front end and actually get some stars on the page. Let's finish up. If I go to my routes file, let's register a route to show an Article. Okay, so articles/{article}, and that will load an ArticlesController at show. All right, that controller doesn't yet exist, so I will create it now, and we'll open that up and add our show method. So we're going to load a view within an articles directory called show, but we want to make sure we pass through the article. So we will accept that using route model binding, like so, and this is what we get.
sure we pass through the article. So we will accept that using route model binding, like so, and this is what we get. Okay, so if I come down to the resources directory views, I'm going to add a new articles directory in a view called show. Now again, we don't have any project here, so I'm just going to spit out a clump of HTML here. So we'll have our app here, and I'm just going to do some quick inline styling. So we'll set a maximum width of 800 pixels and center it on the page. So now I can spit out the title of the article, and then the body. Then below it, we want a rating system.
So now I can spit out the title of the article, and then the body. Then below it, we want a rating system. So we'll just separate it with a quick hr, and this is where the star ratings will go. Okay, I'm about ready to view this in the browser, but first I need at least one article locally. So I'm going to boot up php artisan tinker and then whip one up very quickly. All right, so we have an article with an ID of one. Let's visit that now. articlerating.test/articles/1. Here we go.
Add star rating UI1:43
Article rating.test /articles/one. Here we go. So we have our article, and now once again down here, we're going to have our stars. Now truthfully, writing the CSS for this is actually a little bit tricky. So because it's been done over and over and over, we're going to use an existing solution created by Rachel Bull. There's many different examples, but I found her implementation is fairly simple, which I like. All right, so you can see we can select these. Once we click one, it does remember it using the checked pseudo class.
colors. So I don't need that. Next, this rating is this element here, and you can see I don't need a background color. I don't need borders. I don't need padding. And can we get rid of margin? All right, so now we have this. So let's grab the HTML and switch over, and we're going to paste it in right here. Now we know this will ultimately be wrapped within a form, and that form should submit to articles/{id}/rate, and that will be a POST request.
Now we know this will ultimately be wrapped within a form, and that form should submit to articles/{id}/rate, and that will be a POST request. So we'll add all of the necessary stuff, our CSRF field, and we get this. Okay, so notice for each input, we're using a radio button, but they all have the same name, and that's because we're only selecting one here, 5, 4, 3, 2, or 1 stars. And then we have a label next to each one. And as I understand it from a quick perusal here, the label is what actually applies the star. So you'll see we have the label where we position it, and then right before it, using the pseudo class, we use this character here, and that is the star.
So you'll see we have the label where we position it, and then right before it, using the pseudo class, we use this character here, and that is the star. So let's copy this, or if you want the compiled CSS in CodePen, you can convert it there. So I'm going to switch back to phpStorm, and within my SASS directory, I'm going to set up a component for the star rating, and I'll paste that in there. Now here, yeah, right here, I will import starRating, and I want to compile all of this down. So it sounds like I need to install my various NPM dependencies for Laravel, and while that's doing its thing, we do want to reflect it on the page. So again, I'm using a whole block of HTML here, in real life, you'd have this in a layout.
doing its thing, we do want to reflect it on the page. So again, I'm using a whole block of HTML here, in real life, you'd have this in a layout file. Okay. So this is going to compile to the app.css file, and then down here, this will compile to js.app.js. And again, if this is confusing at all, this is all specified here in your webpack.mix file. Okay, so that's all pulled in. I'll boot up our watcher, so we can keep an eye on our view or our SASS for dependencies,
Create Vue component5:13
That's our next step. So what I'm going to do is move this to a dedicated view component, right here, we'll call it starRating.view, we'll have our templates, our script, you could even put the Sass, by the way, for starRating, you could put that directly within the view file if you want. All right, so it looks like we're going to take all of this and move it over. But now we have certain things, we don't need CSRF if we're doing an AJAX request, but we are expecting the article object, so we can calculate the ID. In these situations, what I'll often do is pass it in from the outside, like this. So if I switch back, we're going to have our starRating component, and I'll set the action, again, from the outside here.
So if I switch back, we're going to have our starRating component, and I'll set the action, again, from the outside here. Now a cool thing, for any attributes you add to a component that are not registered as props, they will be added to the root element of your view component. So in this case, we don't have an action prop declared, so that will be applied directly to the form element, if you didn't know that. So now, if I switch over, give this a refresh, we don't see anything, and that's because we haven't registered starRating as a global view component. So I'll switch back, here's our main entry point, and right down here, we'll override example.
So I'll switch back, here's our main entry point, and right down here, we'll override example. Star rating, again, a global view component is fine for the example. Like so. Okay, so now, remember, we're running npm run watch behind the scenes, so that should compile down. If I give it a refresh, we now have the stars back. The next order of business is to listen for when we select one and submit the necessary AJAX request. So let's say, if you change one of these inputs, we will record the rating.
Submit rating via AJAX6:57
AJAX request. So let's say, if you change one of these inputs, we will record the rating. Alright, let's set up a method for that. And it sounds like I need to track which one you choose. So what I'll do here is add v-model to all of these. We'll call it rating. Okay, let's set that up. Like so. So now take a look. If I open up Chrome DevTools and we'll go to view, here's our rating, but if I select
So now take a look. If I open up Chrome DevTools and we'll go to view, here's our rating, but if I select one now, you can see it's 4, 3, 5, 1. Great. So now that we can track this, all we have to do here is submit an Axios request. Axios.submit a host request to the action here. So I don't want to have to rewrite that, so I'm just going to say, how do we want to do this? We could give it a ref, call it form, and then I can just say, give me the form, and then give me the action.
We could give it a ref, call it form, and then I can just say, give me the form, and then give me the action. Now as part of that, we need to send through the rating, and that should do it. Okay, so let's give this a shot, but before I run it, what I'm going to do in a new tab is boot up php artisan tinker. I'm going to find that article, and at the moment, there should be no ratings, of course. Okay, let's select one. We'll give it five stars. Now we're not getting much feedback here, so let's switch to the network tab. Ah, okay, so here's the request.
Now we're not getting much feedback here, so let's switch to the network tab. Ah, okay, so here's the request. Actually a little tip. You can, if I give it a refresh, let's only look at XHR requests, and if you're getting some kind of page flicker, it can be useful to enable preserve log. Okay, but anyways, I'll do it again, and now we can see there's an issue. We have a 401 Unauthorized. Okay, this makes sense. You're trying to write an article, but you're not signed in, and we decided you have to be registered in order to rate the articles.
Require login to rate8:57
You're trying to write an article, but you're not signed in, and we decided you have to be registered in order to rate the articles. So let's update this by saying only if you're signed in should we show the star rating. Okay, but now if I give it a refresh, I don't see anything. So let's go ahead and register. Now I can't remember if we did this a couple of episodes ago, but I ran php artisan make:auth, and that quickly scaffolds registration and login system. It's all we need for the example. So I'm going to register myself, and now we're signed in. So if I return to that article, we can once again give this a shot.
So I'm going to register myself, and now we're signed in. So if I return to that article, we can once again give this a shot. Okay, so let's go to the network tab. I will give it five stars, and let's see. So we did submit a POST request to the correct endpoint, and if I scroll down, here's the payload. So I think this should work, because once you hit the endpoint, we've already ensured it works with TDD. Okay, so if I come back, we run that query again to fetch all of the ratings for the article, and sure enough, the User with an ID of 1 gave it a 5 rating.
Okay, so if I come back, we run that query again to fetch all of the ratings for the Article, and sure enough, the User with an ID of 1 gave it a 5 rating. Now don't forget though, if they try to update it, let's give it a refresh, they'll give it maybe a 1. Okay, well that doesn't add a new rating that updates their existing one. So now we can see they gave it a 1. But now once again, if I give it a refresh, it should remember the rating you gave it. Now we can of course do this in a couple ways. We could do local storage, we could do a little Redis database, but we're going to stick with the MySQL implementation we have.
We could do local storage, we could do a little Redis database, but we're going to stick with the MySQL implementation we have. So let's come back, and it sounds like we want to give it a default rating when you load the page. Let's do this here. The default will be, well let's just hard code it. So we'll say if the default is 4, then we want to make sure when you load the page, the initial rating, in fact we may change that. The initial rating is 4. Okay.
The initial rating is 4. Okay. So we need to add this as a prop, we'll do it here. And then, because we're using v-model, well let's set the default rating to what you passed in. So if we pass in, in this case, 4, that means the one with a value of 4 should be selected. Give it a refresh, and there we go. So if I were to change this to 2, refresh, now it works. Nice and simple. But I don't want to hard code 2, I want it to be what the user rated it.
Persist and preload rating11:24
Nice and simple. But I don't want to hard code to, I want it to be what the user rated it. Maybe we could have something like articleRating 4, the current user, but the only problem is that doesn't exist yet. We don't have a method called rating4. So let's knock that out super quick. We'll say within ArticleTest here, how about this one, it can fetch the user's, or a user's rating. Alright, so I can reuse some of this. So let's say the user we created here gives it a 5.
Alright, so I can reuse some of this. So let's say the User we created here gives it a 5. And now, if I were to call that ratingFor method, and we give it the User, that should equal 5. Let's say this assertEquals 5 for the rating. Okay, so I'm going to give it a run, and the method doesn't exist, of course not. So Article, right down here, how about this, ratingFor, and we accept a User. Run it again. Okay, of course we're returning null right now. So how do I fetch the rating of an Article for a User?
Okay, of course we're returning null right now. So how do I fetch the rating of an Article for a User? Well remember, I'm on the article instance here, so I could say give me the article's ratings, but only the ones where the user_id is this user you gave me, and then give me the value of rating. So for that first match you find, the user's rating, give me what they selected, or null, and we'll return that. So if I give that a run, now it does return green. Okay, great. So I think we should be able to switch back now.
Okay, great. So I think we should be able to switch back now. This hopefully will be what the user rated it. We'll come back to Chrome, open up View DevTools, give it a refresh. Oh, we're getting a 404. Okay, I know what this is. So notice we lost our articles. So in fact, if we run this again, yeah, it's gone. And that's because for brevity, we've just been using the local database for our tests, but we of course don't really want that.
And that's because for brevity, we've just been using the local database for our tests, but we of course don't really want that. So instead, I'm going to go to my phpunit.xml file, and I'm going to add a new environment variable, and these will override what you specified in your .env file. So I'm going to set DB_DATABASE equal to a database in memory. Okay, so now locally, we're using a simple file database, but now whenever I run my phpunit test, we're just going to whip it up in memory. So if we give this a run, those are still passing, but now they won't interfere with our local database. So that does mean very quickly I have to whip up another article, and also sign in.
our local database. So that does mean very quickly I have to whip up another article, and also sign in. Okay, now we're back to normal. So we load the page now, but I don't see anything, probably because of a JavaScript issue. Yep. The value for a v-bind expression cannot be empty, and that's because at this moment, the User hasn't rated the article. So if we switch back, initial is being set to null. So we could, you know, force 0 to be the default.
So if we switch back, initial is being set to null. So we could, you know, force 0 to be the default. Yeah, and that would fix it, and then we can select, and this will be updated on the fly. So now as we select one, we submit an AJAX request. Let's do it again. We'll give it 1 star, and now we've updated this. So if I come back and refresh, you'll see it does remember that I gave it a 1 star rating. Let's give it 3 star, refresh, and it remembers it. Now don't forget, in this case we're using it for your personal rating, but if you want,
Let's give it three star, refresh, and it remembers it. Now don't forget, in this case we're using it for your personal rating, but if you want, it can also be the average rating, or you could even show the average rating below. So you could say average rating is articleRating. Now here's what everyone has given it, and it just so happens to line up because there's only one rating, so of course it's going to be identical. And that's basically it. At this point, you're mostly tweaking it, deciding what specifically you need. But now we've wrapped it up in a nice view component here called StarRating, and if you want, you could even tweak this.
But now we've wrapped it up in a nice view component here called StarRating, and if you want, you could even tweak this. You could move it over to a V4 if you wanted, but you know what, I think it's fine as is. So if you select any one of these radio buttons, we will call the recordRating method. That will submit an AJAX request to update the rating in the system. Alright, so I'll make all of this code available on GitHub. If you have any questions or you want to tweak it a little bit and you don't know how, just ask a question in the comments below, and one of us will surely help you.
