Tracing Filtering Request0:00
I think we should have a look at how table filtering works. So here we have a context list, and if I type any characters, it'll filter it down according to the name, or organization, or email address. Let's see if that works. We have Megan is Rafaela, okay. Yeah. Okay, so let's figure out how this works. I will begin at the endpoint, /contacts. Go to our routes/web.php file. Here is the contact endpoint.
Controller Data Preparation0:27
Go to our routes/web.php file. Here is the contact endpoint. That will hit a ContactsController. And here we go. Okay, so this is the first piece of the puzzle. So we load this Vue component, and we're passing through as props a list of filters, a list of contacts, and this section might look a little complicated, but it's really not, if you have a look. Get the authenticated user. A User has an account.
Get the authenticated user. A User has an account. Let's get all of the contacts associated with that account. We will eager load the organization, and that's because, well, you have a contact here. There's a relationship to an organization. We have to display it in the table, so let's eager load it. Next, we're going to order the results according to the name. So notice here, it's an alphabetical order, A's down to C, D. You get the idea. Next, filter. So I'm imagining this is either a package or a simple query scope.
Next, filter. So I'm imagining this is either a package or a simple query scope. Let's have a look. So I'm going to contact, filter, yeah, it's a query scope. So let's make a mental note to come back to that. We're going to paginate the results, of course, because we have pagination down here at the bottom, and then with query string, this simply makes sure that the query string is included as part of the paginator, all right? And then finally, through. Through is very similar to map.
And then finally, through. Through is very similar to map. If we can have a look real quick. Can I find it? Right here. It will grab all the items and transform it, and then transform is very similar to map. The only difference is map will return a new collection or a new set of items, whereas transform will update the existing set. Anyways, just think of it as similar to map, but it updates the original collection. And again, it's so you can return a subset of only the data you require.
Anyways, just think of it as similar to map, but it updates the original collection. And again, it's so you can return a subset of only the data you require. So if we have a look here, DevTools, Inertia, let's go down to the page component. Yeah, notice contacts, here's your pagination information, data. The data only contains the information you care about, right? So if we were to simply skip this step, come back, let's give it a refresh. Now you're going to have a whole bulk of information that never gets used on the page, and you've got to be a little careful about this. It's an easy trap to fall into, where you're passing way too much data, and you might hit the limits, or you might expose information that you don't want the end user to see, okay?
Table and Filter UI3:18
So let's see what's going on here. Here's our table. Notice we have headings for name, organization, city, and phone, and that maps to here. All right, we got this. Then it iterates over contacts.data. So this is going over the contacts that we passed from the controller. You just do data because of the pagination that we added. Same thing, though. For each one, add a table row with the contacts details, as you see here. And that makes sense.
For each one, add a table row with the contacts details, as you see here. And that makes sense. But yeah, we know if I type here, it instantly refreshes the page, which means somewhere we are listening for an input event here. And when the user types a character, we make an AJAX request to fetch a new list of contacts. And as you can imagine, that's probably where this section is coming into play. An AJAX request is made here. This runs again. It takes into account any filters you've added, and that returns a new collection. When it does, presumably this re-renders an updated list of contacts.
It takes into account any filters you've added, and that returns a new collection. When it does, presumably this re-renders an updated list of contacts. Okay. So let's figure out how this is working here. Do we see any reference to an AJAX call? No, but I do see this. And I think inertia.replace is made—I'm not familiar with replace. I think that's related to making a GET request. Maybe replacing the history state. I'm honestly not sure, so we're going to have to take a look at that.
Watching Form and Query4:39
Maybe replacing the history state. I'm honestly not sure, so we're going to have to take a look at that. But I do see this watcher for form. So what is form? form is an object for the filters—okay, let's see what's going on here. Bring it back one more time. Filters—I'm sorry, I'm looking at this for the first time. Filters is SEARCH and TRASHED, and we passed that through here. Okay. So we're going to look in the query string for anything related to SEARCH and TRASHED,
Now they are updated, and notice the query string includes those. So if I removed some of this, that would be updated. Okay, great. So if we come on back, we passed through filters, so the filters are accepted as a prop. But I assume we're using v-model on this form. Yeah, form—notice v-model, form search, form trashed. Yeah, so this is kind of a Vue2 thing. If you're accepting prop data, but then you want to modify that potentially, you can't do it. You can't mutate it.
do it. You can't mutate it. So what you end up having to do is have a data property that references the prop. And sometimes you end up in this trap where you want the data property to be called filters as well, but you can't do that. So sometimes you have to choose unique names. Some people will use some kind of prefix like dataFilters, and then this would be filters itself. It's kind of an annoying thing you have to deal with in Vue. So notice in this case, FILTER, form is really the filters, but it's just a way to make it.
Using Lodash pickBy6:58
right? So you want to throttle it to a reasonable number of calls. That way you don't have unlimited AJAX calls. But yeah, we get the query, and pickBy, I'm assuming, is a lodash—I don't know this lodash function. Let's have a look. All right, lodash pickBy. All right, it creates an object composed of the object properties predicate, returns, true, and for. Just show me an example.
TRUTH, and FOR. Just show me an example. Where are we going? Oh, that's not working. Well, you can see it a little bit. So if we have an object, A1, B2, and we PICK—okay, so it's kind of like FILTER, but for objects. So you can see you give it an object, and we're going to effectively filter it down, I think, to only the ones that pass this condition. So if each value is a number, it gets included in the result set. Notice right up here—and I'm sorry, there's no syntax highlighting there—but notice
So if each value is a number, it gets included in the result set. Notice right up here—and I'm sorry, there's no syntax highlighting there—but notice B is actually the string to, not the number. So if you pass all of them to ISNUMBER, you only get the ones that actually are—yeah, it's basically FILTER, but for objects. So let's see what he's doing here. We have the form, PICKBY, there is no PREDICATE—don't you have to give one? So I guess that just defaults to TRUTHY. So give me this object, but filter it down to only the ones where the value is TRUTHY, where it's not NULL.
So give me this object, but filter it down to only the ones where the value is TRUTHY, where it's not NULL. Okay, let's see. console.log. So I assume if I only type into the search string, I'll get an object that doesn't include TRASHED. Let's see. We type something, come back to Chrome DevTools, inertia.replace has been depri—uh-oh. We're gonna have to take a look at that. But first, it still works, but it looks like there's a deprecation notice.
We're gonna have to take a look at that. But first, it still works, but it looks like there's a deprecation notice. Probably the ping app needs to be updated. Yeah, notice here we get an object with SEARCH, but not TRASHED. Okay. So we only want the filters that are relevant. Okay, so then this inertia.replace, and what do we have here? This is a long one. So this is all the r—you know what I'm gonna do, I'm just gonna extract this to route, so it's easier for me to see what's going on here.
So this is all the r—you know what I'm gonna do, I'm just gonna extract this to route, so it's easier for me to see what's going on here. So we want to route to the context page, and we're gonna pass to it object.queries. So do we have anything from this result set? So have any of those filters been populated? If so, send that through, otherwise send through remember, forget. I have no idea what that is. Is that—no, that's not part of inertia, this is part of Ziggy, but I doubt Ziggy has anything. What is remember, forget?
stuck in the weeds. Let's come on back. So if we make some kind of AJAX call to what seems to be the current route, that route then receives the filters. So again, just want to make sure we're all on the same page here, including me. You pass through the search. So notice in the headers, that's being passed along with the query string. So now in your ContextController, this action will run again. But notice this time, it's going to call this query scope, and I think it's time to have a look at what's going on there.
Eloquent Filter Query Scope11:55
But notice this time, it's going to call this query scope, and I think it's time to have a look at what's going on there. Because I actually have something similar for the Lyricast code base. It's a nice, clean way to filter an Eloquent model's results. Okay. So let's have a look at contact scope filter. Now this is going to accept any filters that were passed from that view component. And then we have two query wins. So win, if you're not familiar with this, it's an object-oriented way to basically say, if we have anything for the search, then add this query scope.
Okay. So let's have a look. If we have anything to search by, and we know, let's get rid of this, in this case we did. So that means this callback will run, and we're going to say query, the Eloquent query, give me all of the results where the firstName is similar to the search. And if you've ever done this before with MySQL, this will be super common to you. Percent means any character. So we're saying, look in that firstName column and give me the results that include the search string. And there could be anything before it or anything after it.
But if you wanted to do that, you would do something like this. Now you're saying, check the first_name column and return any results that start with what the user typed into that search box, and then zero or more things can follow it. So if I came back again, notice right now it's returning Bethel Abernathy. But if I do it again, no more. We would actually have to type Abernathy for those results to be returned. Anyways, you get the idea. Let's bring it back to how he had it. So find anything where the first_name matches what you typed, or the last_name, or the email, or where the organization's name matches what you typed.
So find anything where the first name matches what you typed, or the last name, or the email, or where the organization's name matches what you typed. So that includes this. So if I typed Cronin, it's going to return all of the people who are part of that organization. Okay, and then the same thing is going to be true. We haven't looked at this, but I think it's just a withTrashed, yeah. This would include anything that has been soft deleted. Very easy way to go about this. If you only have two or three filters, you could take an approach where each of these are their own classes that get resolved.
If you only have two or three filters, you could take an approach where each of these are their own classes that get resolved. But for simple things like this, it's really nice to put them directly on the model. Okay, so I think we're all on the same page here. The only confusing part might be, well, actually two things. First, I want to figure out exactly what replace does. And then second, we might want to check to see, well, where exactly are we listening for when the user types into that search field? So let's do those two things, and then we'll be done. Okay, so first, come back to inertia replace.
Replacing inertia.replace15:06
So let's do those two things, and then we'll be done. Okay, so first, come back to inertia replace. We're noticing the demo app probably needs an update, because we have this deprecation notice. So let's have a look, and I'll look for replace, and inertia replace deprecated. Okay, so I guess it's just to make the API simpler. So before you had inertia replace, but now you're going to switch it to make a GET request. And what do we have here? An app that arbitrarily sets preserveState true without any indication that it's doing this.
An app that arbitrarily sets preserveState to true without any indication that it's doing this. Okay, so could we change this to inertia.get? Let's come on back. Refresh. Does that still work? It seems like it, Megan. Ooh! Okay, it's still working, but every time I type, I lose focus. So I'm assuming that's what preserveState was doing, and I lost it.
Okay, it's still working, but every time I type, I lose focus. So I'm assuming that's what preserve state was doing, and I lost it. So I need to include that back. Second argument is what? The data. There's no data in this case. Third argument would be preserve state true. So what does preserve state exactly do? I mean, I have a basic idea, but you can preserve a page component's local state using the preserve. This can also be applied to inertia link.
I mean, I have a basic idea, but you can preserve a page component's local state using the preserve. This can also be applied to InertiaLink. That's useful. It'll prevent a page component from fully re-rendering, especially helpful with forms, since you can avoid manually repopulating input fields and also maintain a focused input, which is what we're running into. So I think this will fix it. I type. Yeah. And it seems like we're getting the same thing.
v-model Input Event Flow18:12
then you emit an input event when the user types into it. And that's how we can handle that. So now when the user types in, what they typed in is emitted as an event, and then right here v-model will effectively listen for that event and keep form.search in sync. form.search is down here, and we're all set. This updates. This watches for any kind of update on that form, and when it does, it makes a new request to your server. The server reads the filters, it returns a subset of data, that subset will re-render the component, the contacts will then change, because we now have a subset of what we had.
The server reads the filters, it returns a subset of data, that subset will re-render the component, the contacts will then change, because we now have a subset of what we had before, which means, of course, this will re-render. And when we go through all of the contacts, it will now be that filtered subset of contacts. I think I got it.
