Overview
This article details an approach to closing a Filament's dropdown (Blade component) programmatically using Alpine.js.
We'll create a custom Alpine.js magic function and call that function in the @click handler of individual dropdown items.
Our magic function will be defined in a dedicated JavaScript file and included via Filament's asset registration.
Use case
When using Filament's dropdown Blade component, you may want to close the dropdown panel upon an item being clicked.
Examples of when you'd want to do this include:
- utilizing the dropdown component in a custom Filament field
- creating a custom action menu using the dropdown component
Assumptions
You have a working Laravel project with:
- Filament 4 installed and configured
- a Blade template containing a Filament dropdown component you want to close programmatically using JavaScript
Creating the Alpine magic function
Create the following JavaScript file at a resource path that makes sense to you (e.g. resources/js/functions/close-dropdown.js).
1document.addEventListener('alpine:init', () => {2 Alpine.magic('closeDropdown', (el) => () => {3 const dropdown = el.closest('[x-data*="filamentDropdown"]');4 if (dropdown) Alpine.$data(dropdown).close();5 });6});
In the above JavaScript snippet, we're using the alpine:init lifecycle hook, which fires:
after Alpine is loaded, but BEFORE it initializes itself on the page
Within the hook's callback, we register a new "magic" function named closeDropdown. The callback returns a function
to be invoked whenever $closeDropdown() is called within an Alpine.js component.
The first argument to the callback, el, is the DOM element the magic function was triggered from. Using this DOM
element, we find the closet DOM element that matches the query selector [x-data*="filamentDropdown"].
filamentDropdown is the name of the Alpine data provider Filament uses internally for the dropdown component.
Then we use the Alpine magic function $data, scoped to the Filament dropdown DOM element, to call the close function on the
dropdown's Alpine data instance. The close function we're calling is the same function Filament's internals use to
close the dropdown in Filament Action menus.
Registering the script as a Filament asset
Now that we have a custom Alpine magic function in a dedicated JavaScript file, we need to register the asset using the
mechanism provided by Filament. This will cause our JavaScript file to be included wherever the @filamentScripts Blade
directive is included.
Add the following in the boot method of a service provider:
1<?php 2 3namespace App\Providers; 4 5use Filament\Support\Assets\Js; 6use Filament\Support\Facades\FilamentAsset; 7use Illuminate\Support\ServiceProvider; 8 9class AppServiceProvider extends ServiceProvider10{11 public function boot(): void12 {13 FilamentAsset::register([ 14 Js::make('close-dropdown', __DIR__.'/../../resources/js/functions/close-dropdown.js'),15 ]);16 }17}
Publishing Filament assets
Don't forget to run the following artisan command to publish your updated assets to the /public directory:
1php artisan filament:assets
Using the magic function
To use our new Alpine magic function, we simply need to call $closeDropdown() within the @click handler of a
Filament dropdown list item. Which items make sense to close the dropdown on click depends on the use cases of your application.
1<x-filament::dropdown> 2 <x-slot name="trigger"> 3 <x-filament::button> 4 Dropdown trigger 5 </x-filament::button> 6 </x-slot> 7 8 <x-filament::dropdown.list> 9 <x-filament::dropdown.list.item @click="$closeDropdown()"> 10 Clicking this item will close the dropdown11 </x-filament::dropdown.list.item>12 <x-filament::dropdown.list.item>13 Another item which does not close the dropdown14 </x-filament::dropdown.list.item>15 </x-filament::dropdown.list>16</x-filament::dropdown>
$wire object to call methods on your Livewire component is quite powerful!
Conclusion
The impetus for this article was my surprise in not finding a straightforward way to accomplish the goal of programmatically closing a Filament dropdown when using the provided Blade component.
If anyone knows of a less involved approach than creating a custom Alpine.js magic function that directly accesses Alpine's
data through $data, I'd love to hear about it!