DEV Community

Rails Designer
Rails Designer

Posted on • Originally published at railsdesigner.com

Shift+Click Selection for Bulk Actions with Stimulus

This article was originally published on Rails Designer


Ever needed a “bulk actions” on a list of resources in your Rails app? It is a very common pattern/feature for SaaS apps where users can add many resources (like in admins, CMS', etc.).

Something like this:

This code is based on Rails Designer's Bulk Actions Component. 👀

Let's start with the HTML structure for our selectable list: create a list of items that can be selected with a Shift+Click:

<ul data-controller="select" class="list">
  <% @posts.each do |post| %>
    <li data-action="click->select#toggle" class="item">
      <%= check_box_tag "post_ids[]", post.id, false, hidden: :hidden %>
      <%= link_to post.title, post.href %>
    </li>
  <% end %>
</ul>
Enter fullscreen mode Exit fullscreen mode

Notice each item has a hidden checkbox. You don't need (or want!) to make the checkbox hidden, but based on your app and audience you can pull this off and keep a clean UI. The list is connected to a Stimulus controller named “select”, and each item has a click action that triggers the toggle method in the controller.

Now let's look at the Stimulus controller that handles just the Shift+Click logic:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  toggle(event) {
    if (!event.shiftKey) return

    event.preventDefault()

    const checkbox = event.currentTarget.querySelector('input[type="checkbox"]')
    checkbox.checked = !checkbox.checked
  }
}
Enter fullscreen mode Exit fullscreen mode

Easy! The controller only prevents the default behavior when the Shift key is pressed, allowing normal link clicks to work as expected. When you hold Shift and clicks on an item, the controller toggles the hidden checkbox state.

Now, let's add the actions section that appears when items are selected:

<ul data-controller="select" class="list">
  <% @posts.each do |post| %>
    <li data-action="click->select#toggle" class="item">
      <%= check_box_tag "post_ids[]", post.id, false, hidden: :hidden %>
      <%= link_to post.title, post.href %>
    </li>
  <% end %>

+  <div data-select-target="actions" class="actions">
+    <%= button_to "Archive all", "#", class: "action" %>
+    <%= button_to "Rename all", "#", class: "action" %>
+    <%= button_to "Delete all", "#", class: "action" %>
+  </div>
</ul>
Enter fullscreen mode Exit fullscreen mode

And the code that manages the visibility of the actions:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
+  static targets = ["actions"]
+  static values = { selectedItems: { type: Number, default: 0 } }

  toggle(event) {
    if (!event.shiftKey) return

    event.preventDefault()

    const checkbox = event.currentTarget.querySelector('input[type="checkbox"]')
    checkbox.checked = !checkbox.checked

 +   this.#updateSelectedItemsValue()
  }

  // private

+  selectedItemsValueChanged() {
+    this.actionsTarget.classList.toggle("visible", this.selectedItemsValue !== 0)
+  }

+  #updateSelectedItemsValue() {
+   this.selectedItemsValue = [...this.#checkboxes].filter(checkbox => checkbox.checked).length
+  }

+  get #checkboxes() {
+    return this.element.querySelectorAll('input[type="checkbox"]')
+  }
}
Enter fullscreen mode Exit fullscreen mode
  • classList.toggle accepts an optional argument: the controller uses classList.toggle("visible", this.selectedItemsValue !== 0) to conditionally add or remove the visible class based on whether any items are selected;
  • filter on arrays: use the filter method to count only the checked checkboxes. This creates a clean, functional approach to determining how many items are selected.

In the controller (eg. Posts::BulkController#update) you can work off the post_ids array that is passed along.

And that is it. The basics to add bulk actions to your list of resources. Check out Rails Designer's Components for a more comprehensive Bulk Actions Component that is ready to add to your (SaaS) app.

Top comments (0)