Multiple rows per asset
gap777 opened this issue · 12 comments
Can you think of a way to configure/use datagrid to support multiple rows for each asset? I'm thinking about "subrows", each having a column structure compatible with the main row rendered for an asset, but essentially allowing the main row to be a rollup, and the subrows to be a view into the detailed data contributors. I'd want to hide the subrows with a collapser, and then show them on demand.
Can you draw me the design you want to reach? ASCII art works.
col1 col2 col3 col4
1 asset1-button asset1-col2 data asset1-col3 data asset1-col4 data
2 blank blank asset1-col3* data asset1-col4* data
3 blank blank asset1-col3** data asset1-col4** data
where asset1-col3* data
is a part of the rollup reflected in asset1-col3 data
(as is asset1-col3** data).
And in this case, rows 2 and 3 are initially hidden, and only visible via clicking asset1-button
. Id handle the hiding/showing, but the question Im asking is about the rendering of more than 1 kind of row...
Maybe this could be done with just conditionally logic in the column definitions...
Maybe this could be done with a way to express multiple column definitions for a single column / asset (based on some state).
Maybe this could be done with injecting multiple kinds of assets...
Here is what you can do:
(Note: it requires addtional option I've implemented in master. Please try to use the gem from master directly before I release it).
class MyGrid
class_attribute :tiers_count, default: 3
def self.tiered_column(name, options, blocks)
tiers_count.times do |tier|
block = blocks[tier] || -> { '--blank--' }
column(name, {**options, tier: index}, &block)
end
end
tiered_columns(:col1, {header: "Col 1"}, [
-> (model) { model.first_name },
])
tiered_columns(:col2, {header: "Col 2"}, [
-> (model) {model.last_name},
])
tiered_columns(:col3, {header: "Col 3"}, [
-> (model) { model.method1 },
-> (model) { model.method2 },
-> (model) { model.method3 },
])
def columns_by_tier(tier)
columns.select{|c| c.options[:tier] == tier}
end
def tiers
0..(tiers_count - 1)
end
end
%table
= datagrid_header(@grid, columns: @grid.columns_by_tier(1))
- @grid.assets.each do |asset|
- @grid.tiers.each do |tier|
-# HTML seems to allow multiple tbody tags within the same table
-# I decided to use it for UI control
%tbody.js-tier{data: {tier: tier}}
= datagrid_row(@grid, asset, columns: @grid.columns_by_tier(tier))
I am having some difficulties getting this to work. Is this still supported?
Show me the code you have and the error you get.
I tried the following myself and it worked:
require "datagrid"
Person = Struct.new(:first_name, :last_name, :age, :job)
class TestGrid
include Datagrid
scope do
[
Person.new("John", "Smith", 25, "Developer"),
Person.new("Alfred", "Black", 35, "Manager"),
]
end
def self.tiers
0..(tiers_count - 1)
end
class_attribute :tiers_count, default: 2
def self.tiered_columns(name, options, blocks)
tiers.each do |tier|
block = blocks[tier] || -> (_) { '--blank--' }
column(name, **options, tier: tier) do |model|
block.call(model)
end
end
end
tiered_columns(:col1, {header: "Col 1"}, [
-> (model) { model.first_name },
])
tiered_columns(:col2, {header: "Col 2"}, [
-> (model) {model.last_name},
])
tiered_columns(:col3, {header: "Col 3"}, [
-> (model) { model.age },
-> (model) { model.job },
# -> (model) { model.method3 },
])
def columns_by_tier(tier)
columns.select{|c| c.options[:tier] == tier}
end
def tiers
self.class.tiers
end
end
grid = TestGrid.new
data = grid.assets.map do |asset|
grid.tiers.map do |tier|
[
tier,
*grid.columns_by_tier(tier).map do |column|
grid.data_value(column, asset)
end,
]
end
end.flatten(1)
puts data.map {|row| row.join(',')}.join("\n")
I am trying to use this with the built in views slightly modified from your previous example. It is rendering but not as expected:
class PurchasesGrid < BaseGrid
scope do
Purchase
end
def self.tiers
0..(tiers_count - 1)
end
class_attribute :tiers_count, default: 2
def self.tiered_columns(name, options, blocks)
tiers.each do |tier|
block = blocks[tier] || -> (_) { '--blank--' }
column(name, **options, tier: tier) do |model|
block.call(model)
end
end
end
tiered_columns(:col1, {header: "Col 1"}, [
-> (model) { model.name },
])
tiered_columns(:col2, {header: "Col 2"}, [
-> (model) {model.amount },
-> (model) {model.purchased_at },
])
tiered_columns(:col3, {header: "Col 3"}, [
-> (model) { model.tip },
-> (model) { model.tip_percentage },
# -> (model) { model.method3 },
])
def columns_by_tier(tier)
columns.select{|c| c.options[:tier] == tier}
end
def tiers
self.class.tiers
end
<div class="flex flex-col justify-center items-center">
<% if grid.html_columns(*options[:columns]).any? %>
<%= content_tag :table, options[:html].merge({class: "min-w-full text-left text-sm font-light"}) do %>
<thead class="border-b font-medium dark:border-neutral-500">
<%= datagrid_header(grid, options) %>
</thead>
<% if assets.any? %>
<% @grid.assets.each do |asset| %>
<% @grid.tiers.each do |tier| %>
<tbody class="js-tier" data-tier="<%= tier %>" >
<%= datagrid_row(grid, asset, columns: grid.columns_by_tier(tier)) %>
</tbody>
<% end %>
<% end %>
<% else %>
<tr><td class="noresults" colspan="100%"><%= I18n.t('datagrid.no_results').html_safe %></td></tr>
<% end %>
<% end %>
<% else -%>
<%= I18n.t("datagrid.table.no_columns").html_safe %>
<% end %>
</div>
<tr class="bg-white border-b ">
<% grid.columns.each do |column| %>
<td scope="row" class="<%= datagrid_column_classes(grid, column) + " px-6 py-4 mx-20" %>">
<%= datagrid_value(grid, column, asset) rescue binding.pry %>
</td>
<% end %>
</tr>
In _row.html.erb
, change:
<% grid.columns.each do |column| %>
<%# to %>
<% grid.html_columns(*options[:columns]).each do |column| %>
Not sure why it is grid.columns
for you: https://github.com/bogdan/datagrid/blob/master/app/views/datagrid/_row.html.erb#L2
It is likely a bug. Try the following workaround:
<%= datagrid_row(grid, asset, columns: grid.columns_by_tier(tier)).map(&:name) %>
I'll fix it in the next version anyway
Thanks for getting back to me so quick with fixes. I am still not getting the behavior I think should happen so I made this quick project just run bundle
then rails db:setup
and rails s
to get it up and running. With this config:
tiered_columns(:col1, {header: "Col 1"}, [
-> (model) { model.first_name },
-> (model) { model.last_name },
])
tiered_columns(:col2, {header: "Col 2"}, [
-> (model) { model.age },
-> (model) { model.job },
])
With the HTML changes
I would expect:
Col1 Col2
Test 42
User 1337 Hacker
Let me know if there is any more information you need here.
Made it work like so:
diff --git a/app/grids/users_grid.rb b/app/grids/users_grid.rb
index 18e8578..feaa837 100644
--- a/app/grids/users_grid.rb
+++ b/app/grids/users_grid.rb
@@ -12,7 +12,7 @@ class UsersGrid < BaseGrid
def self.tiered_columns(name, options, blocks)
tiers.each do |tier|
block = blocks[tier] || -> (_) { '--blank--' }
- column(name, **options, tier: tier) do |model|
+ column(:"#{name}_#{tier}", **options, tier: tier) do |model|
block.call(model)
end
end
@@ -27,8 +27,8 @@ class UsersGrid < BaseGrid
-> (model) { model.job },
])
- def columns_by_tier(tier)
- columns.select{|c| c.options[:tier] == tier}
+ def column_names_by_tier(tier)
+ columns.select{|c| c.options[:tier] == tier}.map(&:name)
end
def tiers
diff --git a/app/views/datagrid/_table.html.erb b/app/views/datagrid/_table.html.erb
index e47bc96..1997264 100644
--- a/app/views/datagrid/_table.html.erb
+++ b/app/views/datagrid/_table.html.erb
@@ -7,13 +7,13 @@ Local variables:
<% if grid.html_columns(*options[:columns]).any? %>
<%= content_tag :table, options[:html] do %>
<thead>
- <%= datagrid_header(grid, options) %>
+ <%= datagrid_header(grid, **options, columns: grid.column_names_by_tier(0)) %>
</thead>
<% if assets.any? %>
<% @grid.assets.each do |asset| %>
<% @grid.tiers.each do |tier| %>
<tbody class="js-tier" data-tier="<%= tier %>" >
- <%= datagrid_row(grid, asset, columns: grid.columns_by_tier(tier).map(&:name)) %>
+ <%= datagrid_row(grid, asset, **options, columns: grid.column_names_by_tier(tier)) %>
</tbody>
<% end %>
<% end %>
Thank you! This got it working, the other columns no longer show up but I can work around that.