Skip to content →

Stimulus.js Tutorial – Multiple Selects that Filter Enterprise Data Models

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.

Comments or Questions? Find me on twitter @jpbeatty

Want To Learn More?

Try out some more of my Stimulus.js Tutorials.

Get the next tutorial when it comes out!

Enter your email below, and you won’t miss the next Stimulus JS Tutorial

 

Published in ruby on rails Stimulus JS Tutorial

Comments are closed.