Multi Select Dropdown in rails using daisyUI

Sis Ccr
2 min readMay 19, 2024

--

Recently I integrate daisyUI dropdown component in rails. This was my first time and aslo since rails and js are not my primary language it was hard to figure out how to do it. Most of the tutorials are focused on the drop down using the Select and options.

I am using viewComponent in this but its optional. The concept can be easily transferred to any rails partial.

here is the picture of what I am trying to do.

Tha main Dropdown Component is, I am also using stimulus controller to work with js.

<div data-controller="multi-select">
<details class="dropdown">
<summary class="btn m-1"><%= @title %></summary>
<ul class="dropdown-content menu z-[1] w-52 rounded-box bg-base-100 p-2 shadow">
<% @options.each do |option| %>
<li>
<label class="label cursor-pointer">
<input data-action="change->multi-select#selectItem" data-value = <%= option.to_json %> type="checkbox" class="checkbox-accent checkbox" />
<span class="label-text mx-5"><%= option[:value] %></span>
</label>
</li>
<% end %>
</ul>
</details>
<%= content %>
</div>

The <%= content %> is used to render the hidden_field in rails form. ViewComponent will render any html that is passed to its block in the content. As shown in picture I have checkbox for each items. When we change the value for checkbox data-action=”change->multi-select#selectItem”, selectItem function will be called in the stimulus controller

heres is the actual form element in the _form partial

   <div class="my-5">
<%= form.label :size_id %>
<%#= form.collection_select :size_ids, Size.order(:size), :id, :size, prompt: true, multiple: true, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
<%= render(MultiSelect::MultiSelectComponent.new(title: "Select Size", options: Size.order(:size).map{|s| {id: s.id, value: s.size}})) do %>
<%if admin_product.sizes.present?%>
<span><strong>Selected Size:</strong> <%= admin_product.sizes.map(&:size).join(", ") %></span>
<%end%>
<div data-multi-select-target="hiddenField"></div>
<% end %>
</div>

remember the <%= content %> in the component, <div data-multi-select-target=”hiddenField”></div> this will get render there.

heres my stimulus controller

import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
static targets = ["hiddenField"]

connect() {
console.log("Hello, Multiselect!", this.element);
this.selectedIds = new Set();
}

selectItem(event) {
console.log("select item")
const checkbox = event.target;
const id = JSON.parse(checkbox.dataset.value).id
if (checkbox.checked) {
this.selectedIds.add(id);
} else {
this.selectedIds.delete(id);
}
this.updateHiddenField();
}

updateHiddenField() {
console.log(this.selectedIds);
// Clear existing hidden inputs
this.hiddenFieldTarget.innerHTML = '';

// Create a hidden input for each selected ID
this.selectedIds.forEach(id => {
const hiddenInput = document.createElement('input');
hiddenInput.type = 'hidden';
hiddenInput.name = 'product[size_ids][]';
hiddenInput.value = id;
this.hiddenFieldTarget.appendChild(hiddenInput);
});
}
}

whenever we are changing the value of the checkbox, we are creating a new html input element and appending it the the div that had the hidden target. Notice the name of the input ‘product[size_ids][]’, this is because

the parameter size_ids: [] will be nested under the “product” key

params.require(:product).permit(:name, :details, :const_price, :sale_price, :category_id, :active, images: [], size_ids: [])

--

--

Sis Ccr
Sis Ccr

Written by Sis Ccr

Depths of ocean could not swallow me.

No responses yet