Skip to content

Commit

Permalink
Add Turbo for live search in ui/table component
Browse files Browse the repository at this point in the history
This enhancement enables live search functionality in the products
list table and in general in the `ui/table` component, allowing users
to search for products without reloading the page.
  • Loading branch information
rainerdema authored and elia committed Jul 28, 2023
1 parent 5374fb6 commit 8a6e494
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 56 deletions.
1 change: 1 addition & 0 deletions admin/app/components/solidus_admin/base_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module SolidusAdmin
# BaseComponent is the base class for all components in Solidus Admin.
class BaseComponent < ViewComponent::Base
include SolidusAdmin::ContainerHelper
include Turbo::FramesHelper

def icon_tag(name, **attrs)
render component("ui/icon").new(name: name, **attrs)
Expand Down
113 changes: 59 additions & 54 deletions admin/app/components/solidus_admin/ui/table/component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
url: @base_search_url,
method: :get,
class: 'flex-grow',
"data-#{stimulus_id}-target": "searchForm",
"data-turbo-frame": table_frame_id,
) do |form| %>
<label class="bg-white rounded border border-gray-300 items-center gap-1 px-3 py-1.5 inline-flex w-full justify-start">
<%= render component('ui/icon').new(name: 'search-line', class: "w-[1.4em] h-[1.4em] fill-gray-500") %>
Expand Down Expand Up @@ -66,71 +68,74 @@
</div>
<% end %>
<table class="table-fixed w-full border-collapse">
<colgroup>
<% @columns.each do |column| %>
<col class="<%= column.class_name %>">
<% end %>
</colgroup>

<thead
class="bg-gray-15 text-gray-700 text-left text-small"
data-<%= stimulus_id %>-target="defaultHeader"
>
<tr>
<%= turbo_frame_tag table_frame_id do %>
<table class="table-fixed w-full border-collapse">
<colgroup>
<% @columns.each do |column| %>
<%= render_header_cell(column.header) %>
<col class="<%= column.class_name %>">
<% end %>
</tr>
</thead>
</colgroup>

<% if @batch_actions %>
<thead
data-<%= stimulus_id %>-target="batchHeader"
class="bg-white color-black text-xs leading-none text-left"
class="bg-gray-15 text-gray-700 text-left text-small"
data-<%= stimulus_id %>-target="defaultHeader"
>
<tr>
<%= render_header_cell(selectable_column.header) %>
<%= render_header_cell(content_tag(:div, safe_join([
content_tag(:span, "0", "data-#{stimulus_id}-target": "selectedRowsCount"),
" #{t('.rows_selected')}.",
])), colspan: @columns.count - 1) %>
</div>
</thead>
<% end %>

<tbody class="bg-white text-3.5 line-[150%] text-black">
<% @rows.each do |row| %>
<tr class="border-b border-gray-100">
<% @columns.each do |column| %>
<%= render_data_cell(column.data, row) %>
<%= render_header_cell(column.header) %>
<% end %>
</tr>
<% end %>
</thead>

<% if @rows.empty? && @model_class %>
<tr>
<td
colspan="<%= @columns.size %>"
class="text-center py-4 text-3.5 line-[150%] text-black bg-white"
>
<%= t('.no_resources_found', resources: resource_plural_name) %>
</td>
</tr>
<% if @batch_actions %>
<thead
data-<%= stimulus_id %>-target="batchHeader"
class="bg-white color-black text-xs leading-none text-left"
hidden
>
<tr>
<%= render_header_cell(selectable_column.header) %>
<%= render_header_cell(content_tag(:div, safe_join([
content_tag(:span, "0", "data-#{stimulus_id}-target": "selectedRowsCount"),
" #{t('.rows_selected')}.",
])), colspan: @columns.count - 1) %>
</div>
</thead>
<% end %>
</tbody>

<% if @footer.present? %>
<tfoot>
<tr>
<td colspan="<%= @columns.size %>" class="py-4">
<div class="flex justify-center">
<%= @footer %>
</div>
</td>
</tr>
</tfoot>
<% end %>
<tbody class="bg-white text-3.5 line-[150%] text-black" data-turbo-frame="_top">
<% @rows.each do |row| %>
<tr class="border-b border-gray-100">
<% @columns.each do |column| %>
<%= render_data_cell(column.data, row) %>
<% end %>
</tr>
<% end %>
</table>
<% if @rows.empty? && @model_class %>
<tr>
<td
colspan="<%= @columns.size %>"
class="text-center py-4 text-3.5 line-[150%] text-black bg-white"
>
<%= t('.no_resources_found', resources: resource_plural_name) %>
</td>
</tr>
<% end %>
</tbody>

<% if @footer.present? %>
<tfoot>
<tr>
<td colspan="<%= @columns.size %>" class="py-4">
<div class="flex justify-center">
<%= @footer %>
</div>
</td>
</tr>
</tfoot>
<% end %>

</table>
<% end %>
</div>
13 changes: 13 additions & 0 deletions admin/app/components/solidus_admin/ui/table/component.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Controller } from "@hotwired/stimulus"
import { debounce } from "solidus_admin/utils"

export default class extends Controller {
static targets = [
Expand All @@ -18,6 +19,14 @@ export default class extends Controller {
mode: { type: String, default: "scopes" },
}

initialize() {
// Debounced search function.
// This method submits the search form after a delay of 200ms.
// If the function is called again within this delay, the previous call is cleared,
// effectively ensuring the form is only submitted 200ms after the last call (e.g., user stops typing).
this.search = debounce(this.search.bind(this), 200)
}

connect() {
if (this.searchFieldTarget.value !== "") this.modeValue = "search"

Expand All @@ -29,6 +38,10 @@ export default class extends Controller {
this.render()
}

search() {
this.searchFormTarget.requestSubmit()
}

clearSearch() {
this.searchFieldTarget.value = ""
}
Expand Down
4 changes: 4 additions & 0 deletions admin/app/components/solidus_admin/ui/table/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ def batch_actions_form_id
@batch_actions_form_id ||= "#{stimulus_id}--batch-actions-#{SecureRandom.hex}"
end

def table_frame_id
@table_frame_id ||= "#{stimulus_id}--table-#{SecureRandom.hex}"
end

def render_batch_action_button(batch_action)
render @button_component.new(
name: request_forgery_protection_token,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<nav aria-label="pagination" class="flex items-center">
<nav aria-label="pagination" class="flex items-center" data-controller="<%= stimulus_id %>">
<%= render @button_component.new(
icon: 'arrow-left-s-line',
class: 'rounded-tr-none rounded-br-none border-r-0',
Expand All @@ -9,6 +9,7 @@
'aria-disabled': @prev_link.blank?,
rel: 'prev',
text: content_tag(:span, t('.previous'), class: 'sr-only'),
"data-#{stimulus_id}-target": "prevButton"
) -%>
<%= render @button_component.new(
icon: 'arrow-right-s-line',
Expand All @@ -20,5 +21,6 @@
'aria-disabled': @next_link.blank?,
rel: 'next',
text: content_tag(:span, t('.next'), class: 'sr-only'),
"data-#{stimulus_id}-target": "nextButton"
) %>
</nav>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = ["prevButton", "nextButton"]
static values = { turboFrameId: String }

connect() {
const turboFrame = this.element.closest('turbo-frame')
this.turboFrameIdValue = turboFrame ? turboFrame.id : null

this.prevButtonTarget.dataset.turboFrame = this.turboFrameIdValue
this.prevButtonTarget.dataset.turboAction = "replace"

this.nextButtonTarget.dataset.turboFrame = this.turboFrameIdValue
this.nextButtonTarget.dataset.turboAction = "replace"
}
}
1 change: 0 additions & 1 deletion admin/app/javascript/solidus_admin/application.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
import "@hotwired/turbo-rails"
import "solidus_admin/controllers"
console.log("Hello from application.js")
8 changes: 8 additions & 0 deletions admin/app/javascript/solidus_admin/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const debounce = (func, wait) => {
let timeout

return () => {
clearTimeout(timeout)
timeout = setTimeout(func, wait)
}
}
1 change: 1 addition & 0 deletions admin/config/importmap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
pin "@hotwired/turbo-rails", to: "turbo.js"

pin "solidus_admin/application", preload: true
pin "solidus_admin/utils"
pin_all_from SolidusAdmin::Engine.root.join("app/javascript/solidus_admin/controllers"), under: "solidus_admin/controllers"
pin_all_from SolidusAdmin::Engine.root.join("app/components")

0 comments on commit 8a6e494

Please sign in to comment.