If you have a lot of records that fall into many different buckets, it can be hard to filter all of that complex data record. Thankfully, we can string together a couple select fields to filter based on multiple, disparate categories. This might be like something you’d see on a car shopping website, where you pick the car make, then the makes individual models. We’ll build something similar that joins our book publishers and categories to quickly find our missing book.
I’m continuing to work from my previous tutorials, modeling a complex dataset, and using multi select to pare down a large dataset, which you can find on Github.
Refactor, Refactor, Refactor
Our first refactoring change will be on the index.html.erb
file itself. We’ll add the second select element, and load it with the publishers from our book data.
<div class="small-col">
<h2>Filter Publishers:</h2>
<select name="publishers" multiple size="30" data-action="select-filter#publisherChange">
<%= @publishers.each do |publisher| %>
<option value="<%= publisher %>"><%= publisher.humanize %></option>
<% end %>
</select>
</div>
I’m also adding a second stimulus action, publisherChange
, and changing the old change
method to categoryChange
Our books_controller.rb
gets a new line that plucks all the publishers from our books.
@publishers = Book.all.order(:publisher).pluck(:publisher).uniq
books_filter_controller.rb
gets a whole new configuration. We’d like to filter on categories and publishers, and we need to allow for the possibility that nothing or only one of the sections is selected.
book = Book
book = book.where(category: params[:categories]).order(:category) unless params[:categories].size == 0
book = book.where(publisher: params[:publishers]).order(:publisher) unless params[:publishers].size == 0
book = book.all if params[:publishers].size == 0 and params[:categories].size == 0
@books = book
Refactoring Our Stimulus Controller
We’re going store some state in our controller. In the connect()
function, We’ll add an object to hold on to our previous suggestions.
connect() {
this.filters = { categories: [], publishers: []}
}
Then we either publisherChange(event)
or categoryChange(Event)
fires, we will update the object with the new selection, and hit our API endpoint to retrieve the newly filtered objects.
publisherChange(event) {
this.filters.publishers = getSelectedValues(event)
this.change()
}
categoryChange(event) {
this.filters.categories = getSelectedValues(event)
this.change()
}
Our change method is slightly modified too. We now post the filters object, instead of the selection from one element.
change() {
fetch(this.data.get("url"), {
method: 'POST',
body: JSON.stringify( this.filters ),
credentials: "include",
dataType: 'script',
headers: {
"X-CSRF-Token": getMetaValue("csrf-token"),
"Content-Type": "application/json"
},
})
.then(response => response.text())
.then(html => {
this.booksTarget.innerHTML = html
})
}
I also added a helper function that plucks out the selected values from the selection change event, and puts them neatly in an array.
function getSelectedValues(event) {
return [...event.target.selectedOptions].map(option => option.value)
}
Now we have the ability to filter our books based on two different, independent criteria.
Feel free to leave a comment or question below.
Want To Learn More?
Try out some more of my Stimulus.js Tutorials.
2 comments on “Stimulus.js Tutorial – Multiple Selects that Filter Enterprise Data Models”
[…] Stimulus.js Tutorial – Multiple Selects that Filter Enterprise Data Models […]
[…] controller is a Javascript object, so we can just set properties on the controller. For example, in my tutorial where we use to Stimulus to filter a list based on multiple selects, the controller has a filters property that is created on connect() and updated when any of the […]