Listing and Filtering User Watchlist Items
In this lesson, we'll take our movie filter, and learn how we can reuse it to display a filterable list of the user's watchlist movies.
- Author
- Tom Gobich
- Published
- May 10
- Duration
- 7m 31s
Developer, dog lover, and burrito eater. Currently teaching AdonisJS, a fully featured NodeJS framework, and running Adocasts where I post new lessons weekly. Professionally, I work with JavaScript, .Net C#, and SQL Server.
Adocasts
Burlington, KY
Transcript
Listing and Filtering User Watchlist Items
-
(upbeat music)
-
So now that we can add movies to and from a user's watchlist,
-
let's go ahead and add in a list
-
of what the user has within their watchlist.
-
And we'll add the button to get to this page
-
right up here next to the logout.
-
But first we need to define the route and the handler.
-
So within our watchlist controller,
-
we can scroll on up to our index page
-
and we'll want view and we're also gonna want auth
-
so that we can grab the authenticated user.
-
Right now let's just return view.render
-
and let's scroll down to our pages here to see what we have.
-
There's a couple of different places that we could put this.
-
We could put it inside a movie's watchlist,
-
we could do user's watchlist,
-
or we could just put watchlist out here with our home.
-
Since this will be our one and only page for watchlist,
-
I think probably putting it out there with home
-
is gonna make the most sense.
-
So we'll just do pages/watchlist for our render there.
-
Let's save that real quick
-
and let's go define the route for it.
-
So above our toggle here,
-
let's do router.get/watchlist.
-
I'm gonna leave the S off of this particular route
-
because it will make a little bit more sense for the user
-
since they have that one and only watchlist.
-
Whereas with our post here,
-
it makes a little bit more sense
-
because that's a resourceful action.
-
Then we'll point this to our watchlist controller
-
and our index method inside of there is watchlist.index.
-
We'll keep with the resourceful naming
-
for the actual route name
-
as that won't be user-facing in any way.
-
Now that we have that, let's dive into our navigation
-
and add a link to that page
-
when we have an authenticated user.
-
So this will just be an ahref to our route,
-
watchlists, index, and we'll just call this link watchlist.
-
Then we're gonna want these two
-
to display beside each other.
-
So let's go ahead and wrap it in a div
-
and a class of flex item center gap of maybe three
-
and that div, give this here an indent and that div.
-
And we also have our button text here is text extra small.
-
So let's go ahead and apply that to our link here as well
-
so that they are the same size.
-
All right, give that a save.
-
Jump into our browser real quick just to check it out.
-
And okay, let's also copy that hover padding on that as well.
-
Looks like that might actually just be within our CSS.
-
So let's dive into our CSS real quick.
-
Nav A, let's just apply that to nav button as well.
-
Okay, just check that out once more.
-
There we go, that's a little bit more uniform.
-
So we can't click on this quite yet
-
because we haven't actually created the page.
-
So let's do that next.
-
So within our pages directory, let's right click that,
-
new file, watchlist.edge, hit enter there.
-
And one thing I think would be really nice for this page
-
is if we took what we have for our filters for our movies
-
and apply that to the watchlist as well.
-
So let's go ahead and use our movie index page,
-
which has our filters within it
-
as the base point for our watchlist.
-
Scroll up to the top.
-
We'll change our meta description here
-
to something like movies in your watchlist.
-
Scroll down a little bit
-
because we should also have an H2, there it is.
-
And we'll do the same there.
-
So movies in your watchlist.
-
Give that a save.
-
And let's jump back into our watchlist controller
-
because we need to finish out our index method.
-
Now for this, we can go ahead and use our movies controller
-
as a starting point here as well.
-
So we really just need to give everything here a copy
-
and we'll paste it and replace everything that we have
-
within our index method so far.
-
Updating the view render path to watchlist.
-
Scroll up a little bit here
-
because we will also need our request
-
and we can hit command dot on one of the red squigglies
-
and add all missing imports.
-
Looks like it didn't quite know
-
where to get query string from.
-
So let's try hitting command dot there and there it is.
-
So let's import that.
-
Okay, let's make sure everything's nice and happy.
-
Looks like it.
-
So let's give this a save real quick
-
and give our watchlist page a click
-
just to check and make sure that everything works.
-
And okay, cannot resolve the page
-
at pages watchlist.edge.
-
I wonder if I misspelled it again.
-
Let's go scroll down and see.
-
This time I didn't forget the L but I did forget the T.
-
All right, let's rename that real quick.
-
Can't fix that.
-
Let's jump back into our browser and try that once more.
-
There we go.
-
Okay, so next up we need to filter this down
-
to just movies in our watchlist.
-
So we could dive into our get filtered method
-
and make that change in there
-
but then it would also apply to our movies index page
-
where we're also using this method.
-
We could add in an argument
-
specifying whether or not we want to filter it
-
to just our watchlist movies
-
or we could define another method
-
but there's an alternative approach I think
-
would be a little bit more reusable.
-
So if we dive into our get filtered method,
-
I think what would be most useful
-
is if we just left this to getting our filtered
-
and we move the pagination back into the controller.
-
So if we remove the paginate here,
-
that's going to allow us to apply additional
-
filter statements to this query
-
within the controller itself,
-
making this method a little bit more reusable.
-
So we'll remove the page and the paginate,
-
give that a save once more so that it formats.
-
If we take a look real quick,
-
no red squigglies as we're only being used
-
for the pagination method.
-
Let's first go ahead and dive back
-
into our movies controller
-
and fix the index method up there real quick.
-
So if we scroll up,
-
we no longer want to supply the page into there.
-
And if we temporarily remove the await here
-
and we take a look at the type of our movies,
-
it's now our model query builder contract.
-
We're no longer executing the query
-
since we're not awaiting it anywhere,
-
meaning that we can now do .paginate
-
directly off of our get filtered call
-
as the page number in
-
and specify that we want 15 per page.
-
Now we no longer have that default one
-
that we were applying within the parameter defaults
-
of our get filtered call.
-
So we do want to provide that default here
-
with our request.input as a default value of one there.
-
And then we just need to add the await back.
-
We'll remove that for demonstration purposes.
-
So everything here should be behaving
-
the exact same as it was before.
-
We just move the paginate from inside of our get filtered
-
to now outside of our get filtered.
-
So we want to do that exact same thing
-
within our watchlist controller as well.
-
So remove page there,
-
add in a default page of one on our page,
-
input .paginate, specify the page and save 15 per page.
-
Now, before we take this any further,
-
let's go ahead and dive back into our browser,
-
give it a quick refresh just to make sure
-
that everything still works and it does.
-
Hide that back away.
-
And specifically what that refactoring allowed us to do
-
is now we can break this down
-
and do an additional where has
-
to filter these results down beyond
-
just our get filtered call.
-
So now we can do a where has with watchlist
-
and specify that we only care about the watchlist
-
where query.where user ID is the current auth user ID
-
and our exclamation point there.
-
Now, since this is our watchlist controller
-
and we're going to place an auth middleware on this
-
to require a user to be authenticated
-
in order to get in here,
-
we don't need that if statement wrapping this where has.
-
So we're a-okay to leave this as is.
-
We'll give that a save real quick.
-
And now if we jump back into our browser,
-
give it a refresh,
-
we should only get back movies
-
that are in the user's watchlist,
-
which is exactly what we get.
-
Cool.
-
So the next thing that we need to do is update our form
-
because currently if we were to submit it,
-
it's going to take us back to just our movies index page
-
while we want to be on the watchlist page
-
after we apply a filter.
-
So let's hide that back away,
-
jump back into our watchlist page,
-
and we should just need to update the action here
-
from movies.index to our watchlist.index.
-
We also need to apply that to the base URL as well.
-
So if we jump back into our watchlist controller,
-
scroll down a little bit,
-
the base URL right here,
-
we need to switch that to our watchlist.index too.
-
Give that a save.
-
Now, if we jump back into our browser
-
and try to search by movies with a title containing S,
-
search that, we get back just to,
-
saving private Ryan and stay.
-
If we remove that, run it again,
-
nope, looks like our validator there
-
doesn't like an empty value for that.
-
Probably need to add nullable to that as well.
-
So let's go scroll up to our validators real quick,
-
go into our movie validator,
-
movie filter validator,
-
and let's see, that was our search field.
-
So we have that as optional.
-
Probably also need to just make that nullable.
-
Let's give that a try.
-
Jump back into our browser,
-
try removing that once more,
-
search, nope, still doesn't like that.
-
Okay, well, let's go ahead
-
and just remove this off a numeric.
-
It may be missing up the optional check that we have.
-
Give that a save there.
-
And now if we run this without our title search,
-
everything should be happy.
-
There we go.
-
See, we have one movie in post-production.
-
I'm your boogeyman.
-
So we'll go ahead and do a post-production search there.
-
All right, let's do a released
-
and now put I'm your boogeyman should not be included.
-
Cool, so everything seems to be working a-okay.
-
Let's go check out our pagination real quick.
-
So make sure that that's going to the right page,
-
which it is, and it still has our filters applied,
-
which it does, cool.
-
Introduction
-
Fundamentals
-
2.0Routes and How To Create Them5m 24s
-
2.1Rendering a View for a Route6m 30s
-
2.2Linking Between Routes7m 52s
-
2.3Loading A Movie Using Route Parameters9m 18s
-
2.4Validating Route Parameters6m 7s
-
2.5Vite and Our Assets6m 39s
-
2.6Setting Up Tailwind CSS7m 5s
-
2.7Reading and Supporting Markdown Content4m 32s
-
2.8Listing Movies from their Markdown Files8m 51s
-
2.9Extracting Reusable Code with Services7m 4s
-
2.10Cleaning Up Routes with Controllers4m 52s
-
2.11Defining A Structure for our Movie using Models9m 38s
-
2.12Singleton Services and the Idea of Caching6m 11s
-
2.13Environment Variables and their Validation4m 16s
-
2.14Improved Caching with Redis10m 44s
-
2.15Deleting Items and Flushing our Redis Cache6m 46s
-
2.16Quick Start Apps with Custom Starter Kits6m 28s
-
2.17Easy Imports with NodeJS Subpath Imports8m 40s
-
-
Building Views with EdgeJS
-
3.0EdgeJS Templating Basics8m 49s
-
3.1HTML Attribute and Class Utilities6m 9s
-
3.2Making A Reusable Movie Card Component10m 24s
-
3.3Component Tags, State, and Props4m 53s
-
3.4Use Slots To Make A Button Component6m 56s
-
3.5Extracting A Layout Component5m 13s
-
3.6State vs Share Data Flow2m 59s
-
3.7Share vs Global Data Flow6m 8s
-
3.8Form Basics and CSRF Protection6m 13s
-
3.9HTTP Method Spoofing HTML Forms3m 3s
-
3.10Easy SVG Icons with Edge Iconify7m 57s
-
-
Database and Lucid ORM Basics
-
4.0Configuring Lucid and our Database Connection4m 3s
-
4.1Understanding our Database Schema9m 35s
-
4.2Introducing and Defining Database Migrations18m 35s
-
4.3The Flow of Migrations8m 28s
-
4.4Introducing Lucid Models5m 43s
-
4.5Defining Our Models6m 49s
-
4.6The Basics of CRUD11m 56s
-
4.7Defining Required Data with Seeders11m 11s
-
4.8Stubbing Fake Data with Model Factories13m 48s
-
4.9Querying Our Movies with the Query Builder15m 30s
-
4.10Unmapped and Computed Model Properties3m 24s
-
4.11Altering Tables with Migrations7m 6s
-
4.12Adding A Profile Model, Migration, Factory, and Controller2m 57s
-
4.13SQL Parameters and Injection Protection9m 19s
-
4.14Reusable Query Statements with Model Query Scopes8m 11s
-
4.15Tapping into Model Factory States9m 15s
-
4.16Querying Recently Released and Coming Soon Movies4m 59s
-
4.17Generating A Unique Movie Slug With Model Hooks7m 59s
-
-
Lucid ORM Relationships
-
5.0Defining One to One Relationships Within Lucid Models5m 49s
-
5.1Model Factory Relationships2m 54s
-
5.2Querying Relationships and Eager Vs Lazy Loading5m 17s
-
5.3Cascading and Deleting Model Relationships5m 16s
-
5.4Defining One to Many Relationships with Lucid Models6m 56s
-
5.5Seeding Movies with One to Many Model Factory Relationships5m 24s
-
5.6Listing A Director's Movies with Relationship Existence Queries8m 41s
-
5.7Listing and Counting a Writer's Movies8m 41s
-
5.8Using Eager and Lazy Loading to Load A Movie's Writer and Director5m 18s
-
5.9Defining Many-To-Many Relationships and Pivot Columns9m 48s
-
5.10Many-To-Many Model Factory Relationships4m 50s
-
5.11A Deep Dive Into Relationship CRUD with Models18m 5s
-
5.12How To Create Factory Relationships from a Pool of Data13m 55s
-
5.13How To Query, Sort, and Filter by Pivot Table Data9m 47s
-
-
Working With Forms
-
6.0Accepting Form Data12m 15s
-
6.1Validating Form Data with VineJS9m 29s
-
6.2Displaying Validation Errors and Validating from our Request7m 16s
-
6.3Reusing Old Form Values After A Validation Error2m 3s
-
6.4Creating An EdgeJS Form Input Component5m 28s
-
6.5Creating A Login Form and Validator5m 1s
-
6.6How To Create A Custom VineJS Validation Rule9m 7s
-
-
Authentication & Middleware
-
7.0The Flow of Middleware7m 49s
-
7.1Authenticating A Newly Registered User4m 14s
-
7.2Checking For and Populating an Authenticated User2m 10s
-
7.3Logging Out An Authenticated User2m 24s
-
7.4Logging In An Existing User6m 54s
-
7.5Remembering A User's Authenticated Session6m 55s
-
7.6Protecting Routes with Auth, Guest, and Admin Middleware5m 36s
-
-
Filtering and Paginating Queries
-
8.0Creating A Movie List Page3m 43s
-
8.1Filtering A Query By Pattern Likeness7m 9s
-
8.2Filtering Our List by Movie Status5m 47s
-
8.3How To Apply A Dynamic Sort Filter To Your Query7m 12s
-
8.4Joining SQL Tables To Order By A Related Column4m 49s
-
8.5Validating Query String Filter Values7m 24s
-
8.6How To Paginate Filtered Query Results9m 15s
-
8.7Pagination First, Last, Next, and Previous Buttons4m 3s
-
-
User Watchlist
-
9.0An Alternative Approach to Many-To-Many Relationships4m 56s
-
9.1Toggling A Movie in an Authenticated User's Watchlist9m 56s
-
9.2Listing and Filtering User Watchlist Items7m 31s
-
9.3Allowing Users To Toggle A Movie As Watched4m 44s
-
9.4Filtering By User's Watched Status6m 7s
-
9.5Defining A Composite Unique Constraint4m 47s
-
9.6Persist Filters Easily with Lucid's Query String Method3m 58s
-
-
User Profiles
-
10.0How to Create and Fix Missing User Profiles in Your Application7m 37s
-
10.1Using Dependency Injection to Update A User's Profile9m 46s
-
10.2Saving All Or Nothing with Database Transactions5m 15s
-
10.3Uploading and Displaying User Avatars15m 29s
-
10.4Displaying A User's Profile6m 1s
-
10.5Filtering, Preloading, and Sorting By Relationship7m 6s
-
-
Admin Panel
-
11.0Creating An Admin Layout7m 14s
-
11.1Counting Stats for our Admin Dashboard5m 43s
-
11.2Paginated Admin Movie Table13m 2s
-
11.3Allowing Admins to Create Movies16m 39s
-
11.4Allowing Admins to Update Movies and Clear Values13m 27s
-
11.5How To Use One Form to Create or Edit Movies5m 32s
-
Uploading Movie Cover Images in our Create or Edit Form10m 29s
-
Using A Wildcard Route Param to Download Storage Images7m 57s
-
Posting Objects, Arrays, and an Array of Objects in HTML Forms26m 26s
-
Managed Transactions and Syncing Movie Cast Members15m 55s
-
Allowing Admins to Delete Movies and their Relationships7m 42s
-
Thank You for Watching!0m 31s
-
Join The Discussion! (0 Comments)
Please sign in or sign up for free to join in on the dicussion.
Be the first to Comment!