Need a place for your Laravel app in the cloud? Fly it with Fly.io, it’ll be up and running in minutes!
Oftentimes, we use drop-downs in combination with other html elements to make our website interactive.
One kind of interaction is inter-dependency amongst several drop-downs. This happens when changing a selected option of one drop-down affects the choices given for another drop-down. In our last article, we created dynamic, inter-dependent drop-downs. These drop-downs were dynamically instantiable as they all used one Livewire component as template( named filter
component ). Then, to allow inter-dependency among the drop-downs, we created another component called filter-section
to manage data and data-dependency for each filter
drop-down instance.
Another kind of interaction involves the drop-downs affecting other html elements, like say, filtering data on a table.
Of course, given the nature of dynamically instantiated elements, we need to be able to uniquely identify and track each drop-down instantiated. Furthermore, if our table element is found in a separate Livewire component, we need to find a way to share the data from each filter
drop-down to the table
component.
Today, we’ll get around this data sharing easily with Livewire Modelable and client-side dispatched events.
—Let’s get to it!
Dynamic Drop-Down Set Up
Let’s say we use a filter-section
component to manage a bunch of drop-downs. It would have a public attribute $filters
that contains an array of data which we’ll use to initialize a bunch of drop-downs:
<!-- resources/livewire/filter-section.blade.php -->
<div>
<button>Apply</button>
@foreach( $filters as $key=> $filter )
<livewire:filter
:key="$key.now()"
:label="$filter['label']"
:options="$filter['options']"
/>
@endforeach
</div>
Notice however that each item initializes another Livewire component called filter
. This component contains the template we use to create our drop-downs:
<!-- resources/views/livewire/filter.blade.php -->
<div>
<!-- Show the label of the filter -->
<label>{{ $label }}</label>
<!-- Filter -->
<select wire:model="value">
@foreach( $options as $option )
<option value="{{ $option['id'] }}">{{ $option['name'] }}</option>
@endforeach
</select>
</div>
Let’s say that the $filters
array contains three drop-down data: “Offer Type”, “Property Type”, and “Sub Property Type”.
We should get a view like so:
We’re going to use these drop-downs to show filtered results in a table. The first thing we need to do, then, is to get the selected option of each filter drop-down.
How exactly do we do that?
Uniquely Identifying Drop-Downs
In our setup, we have a parent component filter-section
, and it dynamically initializes a bunch of child filter
components through an array of data. Each filter
component instance contains a drop-down element.
If we want to get the selected option for each drop-down, we’ll have to of course, uniquely identify each one.
So, first, make sure that the $filters
array includes a unique key value for each drop-down data. Then, we’ll need a ‘value’ attribute that will represent the option selected for each drop-down:
public function mount()
{
$this->filters = [
+ 'offer_type'=>[ // Unique key
'label'=> 'OFFER TYPE',
'options'=> OfferType::all()->toArray(),
+ 'value' => 0 // Value attr
],
+ 'property_type'=>[ // Unique key
'label'=>'PROPERTY TYPE',
'options'=> PropertyType::all()->toArray(),
+ 'value' => 0 // Value attr
],
+ 'sub_property_type'=>[ // Unique key
'label'=>'SUB PROPERTY TYPE',
'options'=> SubPropertyType::all()->toArray(),
+ 'value'=>0 // Value attr
]
];
}
With this setup, we can refer to each drop-down’s selected value through the attribute $filters.[drop_down_key].value
.
Now that we have an attribute name to identify each drop-down value, the next step is to link each attribute to its respective drop-down element in our view. In Livewire, we link server attributes with elements in the blade through the use of wire:model
directives:
<!-- resources/livewire/filter-section.blade.php -->
@foreach( $filters as $key=> $filter )
<livewire:filter
...
+ :wire:model="'filters.'.$key.'.value'"
/>
@endforeach
Usually, that’s how we would link attributes to html elements. But notice that filter-section
only has direct access to its filter
child instances and not the drop-down elements we want to wire to.
This syntax then won’t work alone. Because, after all, how would Livewire know which element in the filter
component’s blade should it wire an attribute with?
Enter Modelable: Linking Parent and Child Attributes
Livewire provides us with its Modelable attribute feature. It allows us to match a parent wire:model with any of its child components’ attributes, and ultimately, that child component’s html element.
With it, we can have attributes in our parent component in sync with child components’ attributes!
Applying this directive is just a two-step process. The first step we’ve already done above, which is wiring a specific filter-section
‘s attribute to the instance of its filter
child component.
Next, we need to let the filter
component know where to map the wired model it received. We can do this by attaching “Modelable” to the attribute we want to link with. Since our filter
component has $value
wired to its select element, we want to specifically attach to this attribute:
/* app/Livewire/Filter.php */
+ use Livewire\Attributes\Modelable;
class Filter extends Component
{
+ #[Modelable]
public $value = '';
And that’s it! Now that we’ve attached Modelable to $value
and wired it to the parent’s $filters.[drop_down_key].value
attribute, any changes to $value
would also be reflected in its corresponding $filters.[drop_down_key].value
, thereby giving filter-section
access to the drop-down values found in each instantiation of filter
child components.
Neat, right?
Let’s test that out with a little bit of Alpine and console logging. From our parent component, filter-section
, we have an Apply button. When the user clicks on this button, we can log the value of a specific drop-down:
<button
x-on:click="console.log( $wire.filters.sub_property_type.value )"
>
And…tada! Clicking on the Apply button shows us the value of our intended drop-down!
Sharing Data Outside
Now that we have a way to get each selected drop-down data, let’s finish this by sharing them to another component — our table
component.
The table
component will make use of Livewire’s WithPagination trait to display rows of our data. And to allow filtering, we’ll also add in a public attribute $filterList
which we’ll pass to the pagination query:
/* app/Livewire/Table.php */
use Livewire\WithPagination;
class Table extends Component
{
use WithPagination;
public $filters = [];
public function render()
{
return view('livewire.property-table',[
'rows'=>Property::filter( $this->filters )->paginate(10)
]);
}
}
Then, with the data passed through the $rows
variable in render()
, we create the view which includes the data and pagination links:
<!-- resources/views/livewire/table.blade.php -->
<table>
<thead>
<tr>
<th class="bg-blue-100 border text-left px-8 py-4">Name</th>
<th class="bg-blue-100 border text-left px-8 py-4">Price</th>
</tr>
</thead>
<tbody>
@foreach( $rows as $row )
<tr>
<td>{{$row['name']}}</td>
<td>{{$row['price']}}</td>
</tr>
@endforeach
</tbody>
{{ $rows->links() }}
</table>
Now if we declare this table component in our main page, we should finally get a complete setup like so:
The final question in this article we’ll be answering is, how do we pass data from one component, to another?
Client-Dispatched Events
To share data from one component to another, we usually dispatch events in Livewire. Livewire components can then listen to these events and react accordingly.
To immediately and thriftily dispatch an event, we’ll dispatch one in our blade template.
When the user clicks on the Apply button in our filter-section
component, we want to take the selected drop-downs data it has access to, and share these with the table
component.
To do so, we can dispatch an event containing each drop-down’s selected value from a button click:
<!-- resources/views/livewire/filter-section.blade.php -->
<button
wire:click="$dispatch('apply-filter', {
offer_type_id: $wire.filters.offer_type.value,
property_type_id: $wire.filters.property_type.value,
sub_property_type_id: $wire.filters.sub_property_type.value,
})"
>Apply</button>
Since this apply-filter
event is available on our entire page, we can listen to this from our table
component’s view( or any other component really! ) and react.
We can take the selected drop-down data from event.detail
, and directly update the $filtersList
variable in our table
component with it:
<script>
window.addEventListener('apply-filter', event => {
console.log( 'received event from apply filter in table!', event.detail );
+ @this.set( 'filtersList', event.detail );
});
</script>
And that’s it! We’ve shared our selected drop-down values with the table
component!
Once Livewire updates the $filtersList
attribute in the server using @this.set()
, Livewire would afterward call the render()
method of table
component due to an update to its public attribute.
This time, the $filtersList
will contain data the user selected, and will now be used to filter the rows for our table!