Creating a Custom Filter0:00
One thing that you'll notice with Filament is that a lot of the packages depend on each other. For example, the TableBuilder depends on the FormBuilder, and it uses FormFields to render the filters. Now, filters can render any set of FormFields, and the data from those FormFields is then used to modify the Eloquent query that renders the table. Because we're using the FormBuilder, it's quite easy to make a custom filter. Let's go into our code and pass in a filters array here. Now, all filters start with filter make, with the name of the filter. In this case, I'm going to make a filter for this emailVerifiedAt column. So let's call it emailVerifiedAt. Now we can pass in a custom form to this filter, and this form can contain any FormBuilder component. So let's say a DatePicker for the minimum emailVerifiedAt, so from, and a DatePicker for the maximum, so to. Let's see how this
Grouping Filter Fields0:52
this form can contain any FormBuilder component. So let's say a DatePicker for the minimum emailVerifiedAt, so from, and a DatePicker for the maximum, so to. Let's see how this looks. In our browser, we now have this little filter icon. When we click on it, we can see our from and our to. Now, I think from a design perspective, these two fields could be grouped. So let's wrap them in a fieldset. So fieldset, make, and we can call this emailVerifiedAt. The DatePickers can go in the schema of this fieldset. And by default, fieldsets have two columns in them, so let's put this down to one. And now when we render the filters, we can see that these two fields have been grouped. Now, here we have a layout component and we have two fields. Filters can render any combination of form components like this. Now we actually have to do something with these from and to values. So let's define
Applying Query Constraints1:49
and we have two fields. Filters can render any combination of form components like this. Now we actually have to do something with these from and to values. So let's define a query function. And this is going to be run, and it's going to pass in the Eloquent\Builder that we can then modify, as well as the data from the form. We want to return the query with some modifications. So usually I'll conditionally apply modifications to a query using the when method. And the first argument to the when method is a condition. So in this case, we're going to check if the User has set a from value in this data array. So let's use data['from'], and also check that it's defined. Now in the second argument to this when method, if this condition is true, then this function will be run. So let's pass in the query again, and we're going to modify the query, where date, the name of our column, which is email
if this condition is true, then this function will be run. So let's pass in the query again, and we're going to modify the query, where date, the name of our column, which is email_verified_at, the condition here is going to be greater than or equal to, and the value is going to be the same data from. Now what this is doing is when the User is using this date picker, we are then applying this when, and that scopes the query to where date, and where the date is greater than the from value. So let's check this filter works. So let's go three months ago. And as you can see, all the data is now scoped to three months. Let's do the same for two. So let's add another when, and change this to two, and two there as well. And finally, we can change this operator. So that's opposite. And this is going to allow us to apply maximum date to that email_verified_at. So now we can do, let's say four months.
Extracting a Reusable Filter3:47
as well. And finally, we can change this operator. So that's opposite. And this is going to allow us to apply maximum date to that email_verified_at. So now we can do, let's say four months. And then our maximum is going to be two months. Now the table is filtered, so the only records that were verified two and three months ago are in the table. Now this might be quite a useful filter to reuse between two tables. And to do that, we can extract this into its own class. So in our project, let's make a new class in the tables/filters directory. So app/tables/filters, and I'm going to call this DateRangeFilter. Now this class can extend the BaseFilter. And in the setup method, we're going to do all of the configuration that we would have done here. So let's copy all of this and put it in this setup method. So we're going to call parent::setup, and then we're going to call this and then paste
that we would have done here. So let's copy all of this and put it in this setup method. So we're going to call parent::setup, and then we're going to call $this and then paste everything we had. Now this setup method is common between all Filament components, and it's run just after the component's been instantiated. So for example, right after the make happens, but before it actually gets returned, this is going to run and it's going to provide some default settings for this Filter class. So let's swap out the original Filter class with our own. So DateRangeFilter. And now this should work exactly the same as how we had it before. Let's filter three months ago. Looks great. Now what we can do is make this a bit more generic, because at the moment we have this hard coded in, we have the column name hard coded in. So we can use getters that already exist on the Filter class to
Making the Filter Generic5:42
a bit more generic, because at the moment we have this hard coded in, we have the column name hard coded in. So we can use getters that already exist on the Filter class to make this dynamic. The first getter we're going to use is this getLabel. But since the label hasn't been set by the time that the filter has been instantiated, we actually need to wrap this form in a closure so that it gets evaluated later. Let's use a closure. And now we can use this getLabel. The same with the query, but since it's already a function, we don't have to wrap it in another function. So now we can use this getName in these two places. And since the label of the filter is generated from the name of it, we don't need to set up ourselves. And also we're using the name as the column that we're scoping on. So this should work exactly the same as it did before. But now it's not actually
Adding Max Date Support6:38
need to set up ourselves. And also we're using the name as the column that we're scoping on. So this should work exactly the same as it did before. But now it's not actually dependent on this email_verified column. We can change it to whatever we want. Now, since filters are also component classes, we can use the same pattern of properties, setters and getters to add additional configuration to our custom filter classes. For example, we might want a maxDate that then applies to both of the date pickers in the filter. And you can use this on the filter itself without having to override the form. So let's add our maxDate setter in the filter class, just like we do to any other component class in Filament. So public function maxDate, it's going to return static. So the current object instance, and we're going to be setting a property. So this maxDate equals, and then
in filament. So public function maxDate, it's going to return static. So the current object instance, and we're going to be setting a property. So this maxDate equals, and then let's say date. And the possible values for date could be a string date, it could be a DateTime contract, or DateTimeInterface from php. It could be a closure because we can evaluate in these component classes. It could also be null if the user wants to unset a previously set maxDate. So let's call this date and also set up our maxDate property. So let's copy this type string and paste it up here. So protected types, and then maxDate and set it to null by default. Now we just need our getter. So public function getMaxDate. And this is going to return the same string DateTimeInterface and not closure this time because we're going to evaluate. Now we can call return this evaluate to our
get maxDate. And this is going to return the same string datetime interface and not closure this time because we're going to evaluate. Now we can call return this evaluate to our unwrap that closure. We're going to pass in maxDate. Now these date pickers here are currently using the HTML native date pickers from the browser. Now the current time of recording these don't support setting a maximum date that the user can see within the UI. So I'm going to first make these into our built in JavaScript date pickers from Filament. So I'm going to pass in native false like that. And now I'm going to pass in a maxDate. And I'm going to call this getMaxDate. Since this is going to return our date object, we can pass it straight to this maxDate that also accepts a string closure, Carbon interface for example. Let's pass this in. Too few arguments to function maxDate. Oh yeah, we need to
we can pass it straight to this maxDate that also accepts a string closure, Carbon interface for example. Let's pass this in. Too few arguments to function maxDate. Oh yeah, we need to pass in our maxDate to our dateRangeFilter. So I'm just going to keep it simple and just pass in now so that you can't select any date in the future because users can't be verified in the future. So from and as you can see, we now can't select any of the maxDates in the future, only the ones before. And if we change this to, for example, addMonth and we navigate to the next month, we can see that all the current month's dates are available, but the next month's dates aren't. So this is completely working. We now have our configuration and our custom dateRangeFilter that can be used between multiple tables.
now have our configuration and our custom dateRangeFilter that can be used between multiple tables.
