Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Admin] Add sortable rows in ui/table component #5522

Merged
merged 4 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Add sortable feature to ui/table component and update preview toggles
- Integrate sorting rows functionality with the 
`sortable_controller.js`.
- Enhanced the preview section to allow enabling and disabling all table
  functionalities, including sorting, search, filters, batch actions,
  and scopes.
  • Loading branch information
rainerdema committed Nov 29, 2023
commit a0b8a40841da74ebb39a682d61526efaffd65a8a
11 changes: 10 additions & 1 deletion admin/app/components/solidus_admin/ui/table/component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
data-controller="<%= stimulus_id %>"
data-<%= stimulus_id %>-selected-row-class="bg-gray-15"
data-<%= stimulus_id %>-mode-value="<%= initial_mode %>"
data-<%= stimulus_id %>-sortable-value="<%= @sortable.present? %>"
data-action="
<%= component("ui/table/ransack_filter").stimulus_id %>:search-><%= stimulus_id %>#search
<%= component("ui/table/ransack_filter").stimulus_id %>:showSearch-><%= stimulus_id %>#showSearch
Expand Down Expand Up @@ -122,13 +123,21 @@
</thead>
<% end %>

<tbody class="bg-white text-3.5 line-[150%] text-black">
<tbody
class="bg-white text-3.5 line-[150%] text-black"
data-<%= stimulus_id %>-target="tableBody"
<%= "data-controller=sortable" if @sortable&.url %>
<%= "data-sortable-param-value=#{@sortable.param}" if @sortable&.param %>
<%= "data-sortable-handle-value=#{@sortable.handle}" if @sortable&.handle %>
<%= "data-sortable-animation-value=#{@sortable.animation}" if @sortable&.animation %>
>
<% @data.rows.each do |row| %>
<tr
class="border-b border-gray-100 last:border-0 hover:bg-gray-50 cursor-pointer <%= 'bg-gray-15 text-gray-700' if @data.fade&.call(row) %>"
<% if @data.url %>
data-action="click-><%= stimulus_id %>#rowClicked"
data-<%= stimulus_id %>-url-param="<%= @data.url.call(row) %>"
<%= "data-sortable-url=#{@sortable.url.call(row)}" if @sortable&.url %>
<% end %>
>
<% @data.columns.each do |column| %>
Expand Down
9 changes: 9 additions & 0 deletions admin/app/components/solidus_admin/ui/table/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ export default class extends Controller {
"filterToolbar",
"defaultHeader",
"batchHeader",
"tableBody",
"selectedRowsCount",
]

static classes = ["selectedRow"]
static values = {
mode: { type: String, default: "scopes" },
sortable: { type: Boolean, default: false },
}

initialize() {
Expand Down Expand Up @@ -129,6 +131,13 @@ export default class extends Controller {
checkbox.closest("tr").classList.toggle(this.selectedRowClass, checkbox.checked),
)

// Determine if sortable should be enabled
if (this.sortableValue && this.modeValue !== "batch" && this.modeValue !== "search") {
this.tableBodyTarget.setAttribute('data-controller', 'sortable');
} else {
this.tableBodyTarget.removeAttribute('data-controller');
}

// Update the selected rows count
this.selectedRowsCountTarget.textContent = `${selectedRows.length}`

Expand Down
6 changes: 4 additions & 2 deletions admin/app/components/solidus_admin/ui/table/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ class SolidusAdmin::UI::Table::Component < SolidusAdmin::BaseComponent
Column = Struct.new(:header, :data, :col, :wrap, keyword_init: true)
Filter = Struct.new(:presentation, :combinator, :attribute, :predicate, :options, keyword_init: true)
Scope = Struct.new(:name, :label, :default, keyword_init: true)
private_constant :BatchAction, :Column, :Filter, :Scope
Sortable = Struct.new(:url, :param, :animation, :handle, keyword_init: true)
private_constant :BatchAction, :Column, :Filter, :Scope, :Sortable

class Data < Struct.new(:rows, :class, :url, :prev, :next, :columns, :fade, :batch_actions, keyword_init: true) # rubocop:disable Lint/StructNewOverride,Style/StructInheritance
def initialize(**args)
Expand Down Expand Up @@ -49,11 +50,12 @@ def value
end
end

def initialize(id:, data:, search: nil)
def initialize(id:, data:, search: nil, sortable: nil)
@id = id
@data = Data.new(**data)
@data.columns.unshift selectable_column if @data.batch_actions.present?
@search = Search.new(**search) if search
@sortable = Sortable.new(**sortable) if sortable
end

def selectable_column
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,114 @@
class SolidusAdmin::UI::Table::ComponentPreview < ViewComponent::Preview
include SolidusAdmin::Preview

# Render a simple table with 10 products and pagination.
# - The `:id` column is the product `#id` attribute, and uses a **symbol** for both the content and the header
# - The `:name` column is the product `#name` attribute, and uses **a block returning strings** for the content and a symbol for the header
# - The `:available_on` column is the product `#available_on` attribute, and uses **a block returning strings** for both the content and the header
# - The `:price` column shows the product `#price` attribute in a badge component, uses **blocks returning component instances** for both the content and the header
#
# All these ways to header and data cells can be mixed and matched.
def simple
# @param search_bar toggle
# @param filters toggle "Visible only with the Search Bar enabled"
# @param batch_actions toggle
# @param scopes toggle
# @param sortable select :sortable_select
def overview(search_bar: false, filters: false, batch_actions: false, scopes: false, sortable: nil)
render current_component.new(
id: 'simple-list',
data: {
class: Spree::Product,
rows: Array.new(10) { |n|
Spree::Product.new(id: n, name: "Product #{n}", price: n * 10.0, available_on: n.days.ago)
},
columns: [
{ header: :id, data: -> { _1.id.to_s } },
{ header: :name, data: :name },
{ header: -> { "Availability at #{Time.current}" }, data: -> { "#{time_ago_in_words _1.available_on} ago" } },
{ header: -> { component("ui/badge").new(name: "$$$") }, data: -> { component("ui/badge").new(name: _1.display_price, color: :green) } },
{ header: "Generated at", data: Time.current.to_s },
],
prev: nil,
next: '#2',
data: table_data(batch_actions, sortable),
search: search_bar ? search_options(filters, scopes) : nil,
sortable: sortable ? sortable_options(sortable) : nil,
)
end

private

def sortable_select
{
choices: %i[row handle],
include_blank: true
}
end

def table_data(batch_actions, sortable)
columns = [
{ header: :id, data: -> { _1.id.to_s } },
{ header: :name, data: :name },
{ header: -> { "Availability at #{Time.current}" }, data: -> { "#{time_ago_in_words _1.available_on} ago" } },
{ header: -> { component("ui/badge").new(name: "$$$") }, data: -> { component("ui/badge").new(name: _1.display_price, color: :green) } },
{ header: "Generated at", data: Time.current.to_s },
]

if sortable == "handle"
columns.unshift({
header: "",
data: ->(_) { component("ui/icon").new(name: 'draggable', class: 'w-5 h-5 cursor-pointer handle') }
})
end
{
class: Spree::Product,
rows: Array.new(10) { |n| Spree::Product.new(id: n, name: "Product #{n}", price: n * 10.0, available_on: n.days.ago) },
columns: columns,
prev: nil,
next: '#2',
}.tap do |data|
data[:batch_actions] = batch_actions_data if batch_actions
end
end

def batch_actions_data
[
{
display_name: "Delete",
action: "#",
method: :delete,
icon: 'delete-bin-7-line',
},
search: {
name: :no_key,
url: '#',
{
display_name: "Discontinue",
action: "#",
method: :put,
icon: 'pause-circle-line',
},
)
{
display_name: "Activate",
action: "#",
method: :put,
icon: 'play-circle-line',
},
]
end

def search_options(filters, scopes)
{
name: :no_key,
url: '#',
scopes: scopes ? scope_options : nil,
filters: filters ? filter_options : nil
}
end

def scope_options
[
{ name: :all, label: "All", default: true },
{ name: :deleted, label: "Deleted" }
]
end

def filter_options
[
{
presentation: "Filter",
combinator: 'or',
attribute: "attribute",
predicate: "eq",
options: [
["Yes", 1], ["No", 0]
]
}
]
end

def sortable_options(sortable)
options = {
url: ->(_) { "#" },
param: 'position'
}
options[:handle] = '.handle' if sortable == "handle"
options
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

RSpec.describe SolidusAdmin::UI::Table::Component, type: :component do
it "renders a simple table" do
render_preview(:simple)
render_preview(:overview)
end
end
Loading