katalyst-tables 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +22 -38
- data/app/components/katalyst/table_component.rb +91 -0
- data/app/components/katalyst/tables/body_cell_component.rb +40 -0
- data/app/components/katalyst/tables/body_row_component.rb +40 -0
- data/app/components/katalyst/tables/header_cell_component.rb +54 -0
- data/app/components/katalyst/tables/header_row_component.rb +39 -0
- data/lib/katalyst/tables/backend.rb +8 -8
- data/lib/katalyst/tables/engine.rb +11 -0
- data/lib/katalyst/tables/frontend/helper.rb +17 -8
- data/lib/katalyst/tables/frontend.rb +6 -36
- data/lib/katalyst/tables/version.rb +1 -1
- data/lib/katalyst/tables.rb +5 -0
- metadata +23 -9
- data/lib/katalyst/tables/frontend/builder/base.rb +0 -63
- data/lib/katalyst/tables/frontend/builder/body_cell.rb +0 -31
- data/lib/katalyst/tables/frontend/builder/body_row.rb +0 -29
- data/lib/katalyst/tables/frontend/builder/header_cell.rb +0 -55
- data/lib/katalyst/tables/frontend/builder/header_row.rb +0 -23
- data/lib/katalyst/tables/frontend/table_builder.rb +0 -71
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c4a55f7a1b45c9f70f357eaa670debf6457aca92a8ac7ea8b10df20140d1c23
|
4
|
+
data.tar.gz: 620cff7771f9de2d74c613d6b107136ea34c494862d2da4e612ccb55d8dee51d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15cc508fdbb3ad40baec51314fde01c3889b670d4ccd6366ea49171a45b979d441a4fb3fafe65e5bd07093375615b6d298ebe091f240122edf9d68b10471f251
|
7
|
+
data.tar.gz: e5f4186c070c81c4645bc3cf8a378927f502df6084d235c38416d98eb7e4ab45c775da2d22338d32bf5063774a575b4c7a92a556468f1b35d18c809db421922c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [2.0.0]
|
4
|
+
|
5
|
+
- Replaces builders with view_components
|
6
|
+
We want view components to be the way to build custom tables, and add more
|
7
|
+
complete features to the gem. This will be a breaking change, but we have
|
8
|
+
tried hard to retain compatibility with existing code. Unless you are using
|
9
|
+
a custom builder then it's unlikely that you will see any changes.
|
10
|
+
- If you are using custom builders, then you will need to update them to use
|
11
|
+
view components. See [[README.md]] for examples.
|
12
|
+
|
3
13
|
## [1.1.0] - 2023-07-18
|
4
14
|
|
5
15
|
- Replaces `param_key` with `i18n_key` for attribute lookup in locale file
|
data/README.md
CHANGED
@@ -7,18 +7,16 @@ Tools for building HTML tables from ActiveRecord collections.
|
|
7
7
|
Add this line to your application's Gemfile:
|
8
8
|
|
9
9
|
```ruby
|
10
|
-
gem "katalyst-tables"
|
10
|
+
gem "katalyst-tables"
|
11
11
|
```
|
12
12
|
|
13
13
|
And then execute:
|
14
14
|
|
15
15
|
$ bundle install
|
16
16
|
|
17
|
-
**Reminder:** If you have a rails server running, remember to restart the server to prevent the `uninitialized constant` error.
|
18
|
-
|
19
17
|
## Usage
|
20
18
|
|
21
|
-
This gem provides two entry points: Frontend for use in your views, and Backend for use in your controllers. The backend
|
19
|
+
This gem provides two entry points: `Frontend` for use in your views, and `Backend` for use in your controllers. The backend
|
22
20
|
entry point is optional, as it's only required if you want to support sorting by column headers.
|
23
21
|
|
24
22
|
### Frontend
|
@@ -35,7 +33,7 @@ Add `include Katalyst::Tables::Frontend` to your `ApplicationHelper` or similar.
|
|
35
33
|
<% end %>
|
36
34
|
```
|
37
35
|
|
38
|
-
|
36
|
+
The table builder will call your block once per row and accumulate the cells you generate into rows:
|
39
37
|
|
40
38
|
```html
|
41
39
|
|
@@ -102,14 +100,14 @@ All cells generated in the table header iteration will automatically be header c
|
|
102
100
|
in your body rows by passing `heading: true` when you generate the cell.
|
103
101
|
|
104
102
|
```erb
|
105
|
-
|
103
|
+
<% row.cell :id, heading: true %>
|
106
104
|
```
|
107
105
|
|
108
|
-
The table header cells default to showing the
|
106
|
+
The table header cells default to showing the capitalized column name, but you can customize this in one of two ways:
|
109
107
|
|
110
108
|
* Set the value inline
|
111
109
|
```erb
|
112
|
-
|
110
|
+
<% row.cell :id, label: "ID" %>
|
113
111
|
```
|
114
112
|
* Define a translation for the attribute
|
115
113
|
```yml
|
@@ -261,56 +259,42 @@ You can write a custom builder that helps generate this type of table by adding
|
|
261
259
|
for generating the actions. This allows for a declarative table syntax, something like this:
|
262
260
|
|
263
261
|
```erb
|
264
|
-
<%= table_with(collection: collection,
|
265
|
-
|
266
|
-
|
262
|
+
<%= table_with(collection: collection, component: ActionTableComponent) do |row| %>
|
263
|
+
<% row.cell :name %>
|
264
|
+
<% row.actions do |cell| %>
|
267
265
|
<%= cell.action "Edit", :edit %>
|
268
266
|
<%= cell.action "Delete", :delete, method: :delete %>
|
269
267
|
<% end %>
|
270
268
|
<% end %>
|
271
269
|
```
|
272
270
|
|
273
|
-
And the
|
271
|
+
And the customized component:
|
274
272
|
|
275
273
|
```ruby
|
276
|
-
class
|
277
|
-
def build(&block)
|
278
|
-
(@html_options[:class] ||= []) << "action-table"
|
279
|
-
super
|
280
|
-
end
|
281
|
-
|
282
|
-
def table_header_row(builder = ActionHeaderRow, &block)
|
283
|
-
super
|
284
|
-
end
|
274
|
+
class ActionTableComponent < Katalyst::TableComponent
|
285
275
|
|
286
|
-
|
287
|
-
|
288
|
-
|
276
|
+
config.header_row = "ActionHeaderRow"
|
277
|
+
config.body_row = "ActionBodyRow"
|
278
|
+
config.body_cell = "ActionBodyCell"
|
289
279
|
|
290
|
-
def
|
280
|
+
def call
|
281
|
+
options(class: "action-table")
|
291
282
|
super
|
292
283
|
end
|
293
284
|
|
294
|
-
|
295
|
-
super
|
296
|
-
end
|
297
|
-
|
298
|
-
class ActionHeaderRow < Katalyst::Tables::Frontend::Builder::HeaderRow
|
285
|
+
class ActionHeaderRow < Katalyst::Tables::HeaderRowComponent
|
299
286
|
def actions(&block)
|
300
|
-
cell(:actions, class: "actions", label: "")
|
287
|
+
cell(:actions, class: "actions", label: "", &block)
|
301
288
|
end
|
302
289
|
end
|
303
290
|
|
304
|
-
class
|
305
|
-
end
|
306
|
-
|
307
|
-
class ActionBodyRow < Katalyst::Tables::Frontend::Builder::BodyRow
|
291
|
+
class ActionBodyRow < Katalyst::Tables::BodyRowComponent
|
308
292
|
def actions(&block)
|
309
293
|
cell(:actions, class: "actions", &block)
|
310
294
|
end
|
311
295
|
end
|
312
296
|
|
313
|
-
class ActionBodyCell < Katalyst::Tables::
|
297
|
+
class ActionBodyCell < Katalyst::Tables::BodyCellComponent
|
314
298
|
def action(label, href, **opts)
|
315
299
|
content_tag :a, label, { href: href }.merge(opts)
|
316
300
|
end
|
@@ -318,11 +302,11 @@ class ActionTable < Katalyst::Tables::Frontend::TableBuilder
|
|
318
302
|
end
|
319
303
|
```
|
320
304
|
|
321
|
-
If you have a table
|
305
|
+
If you have a table component you want to reuse, you can set it as a default for some or all of your controllers:
|
322
306
|
|
323
307
|
```html
|
324
308
|
class ApplicationController < ActiveController::Base
|
325
|
-
|
309
|
+
default_table_component ActionTableComponent
|
326
310
|
end
|
327
311
|
```
|
328
312
|
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
# A component for rendering a table from a collection, with a header row.
|
5
|
+
# ```erb
|
6
|
+
# <%= Katalyst::TableComponent.new(collection: @people) do |row, person| %>
|
7
|
+
# <%= row.cell :name do |cell| %>
|
8
|
+
# <%= link_to cell.value, person %>
|
9
|
+
# <% end %>
|
10
|
+
# <%= row.cell :email %>
|
11
|
+
# <% end %>
|
12
|
+
# ```
|
13
|
+
class TableComponent < ViewComponent::Base
|
14
|
+
include ActiveSupport::Configurable
|
15
|
+
include Tables::Frontend::Helper
|
16
|
+
|
17
|
+
attr_reader :collection, :sort, :object_name
|
18
|
+
|
19
|
+
# Workaround: ViewComponent::Base.config is incompatible with ActiveSupport::Configurable
|
20
|
+
@_config = Class.new(Configuration).new
|
21
|
+
|
22
|
+
config_accessor :header_row
|
23
|
+
config_accessor :header_cell
|
24
|
+
config_accessor :body_row
|
25
|
+
config_accessor :body_cell
|
26
|
+
|
27
|
+
def initialize(collection:,
|
28
|
+
sort: nil,
|
29
|
+
header: true,
|
30
|
+
object_name: collection.try(:model_name)&.i18n_key,
|
31
|
+
**html_options)
|
32
|
+
super
|
33
|
+
|
34
|
+
@collection = collection
|
35
|
+
@sort = sort
|
36
|
+
@header = header
|
37
|
+
@object_name = object_name
|
38
|
+
end
|
39
|
+
|
40
|
+
def call
|
41
|
+
tag.table(**@html_options) do
|
42
|
+
thead + tbody
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def thead
|
47
|
+
return "".html_safe unless @header
|
48
|
+
|
49
|
+
tag.thead do
|
50
|
+
concat(render_header)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def tbody
|
55
|
+
tag.tbody do
|
56
|
+
collection.each do |record|
|
57
|
+
concat(render_row(record))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def render_header
|
63
|
+
# extract the column's block from the slot and pass it to the cell for rendering
|
64
|
+
self.class.header_row_component.new(self).render_in(view_context, &@__vc_render_in_block)
|
65
|
+
end
|
66
|
+
|
67
|
+
def render_row(record)
|
68
|
+
# extract the column's block from the slot and pass it to the cell for rendering
|
69
|
+
block = @__vc_render_in_block
|
70
|
+
self.class.body_row_component.new(self, record).render_in(view_context) do |row|
|
71
|
+
block.call(row, record)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.header_row_component
|
76
|
+
@header_row_component ||= const_get(config.header_row || "Katalyst::Tables::HeaderRowComponent")
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.header_cell_component
|
80
|
+
@header_cell_component ||= const_get(config.header_cell || "Katalyst::Tables::HeaderCellComponent")
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.body_row_component
|
84
|
+
@body_row_component ||= const_get(config.body_row || "Katalyst::Tables::BodyRowComponent")
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.body_cell_component
|
88
|
+
@body_cell_component ||= const_get(config.body_cell || "Katalyst::Tables::BodyCellComponent")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
class BodyCellComponent < ViewComponent::Base # :nodoc:
|
6
|
+
include Frontend::Helper
|
7
|
+
|
8
|
+
attr_reader :record
|
9
|
+
|
10
|
+
def initialize(table, record, attribute, heading: false, **html_options)
|
11
|
+
super(**html_options)
|
12
|
+
|
13
|
+
@table = table
|
14
|
+
@record = record
|
15
|
+
@attribute = attribute
|
16
|
+
@type = heading ? :th : :td
|
17
|
+
end
|
18
|
+
|
19
|
+
def before_render
|
20
|
+
# fallback if no content block is given
|
21
|
+
with_content(value.to_s) unless content?
|
22
|
+
end
|
23
|
+
|
24
|
+
def call
|
25
|
+
content # ensure content is set before rendering options
|
26
|
+
|
27
|
+
content_tag(@type, content, **@html_options)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return the object for this row.
|
31
|
+
def object
|
32
|
+
@record
|
33
|
+
end
|
34
|
+
|
35
|
+
def value
|
36
|
+
@record.public_send(@attribute)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
class BodyRowComponent < ViewComponent::Base # :nodoc:
|
6
|
+
include Frontend::Helper
|
7
|
+
|
8
|
+
renders_many :columns, ->(component) { component }
|
9
|
+
|
10
|
+
def initialize(table, record)
|
11
|
+
super()
|
12
|
+
|
13
|
+
@table = table
|
14
|
+
@record = record
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
content # generate content before rendering
|
19
|
+
|
20
|
+
tag.tr(**@html_options) do
|
21
|
+
columns.each do |column|
|
22
|
+
concat(column.to_s)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def cell(attribute, **options, &block)
|
28
|
+
with_column(@table.class.body_cell_component.new(@table, @record, attribute, **options), &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def header?
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def body?
|
36
|
+
true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
class HeaderCellComponent < ViewComponent::Base # :nodoc:
|
6
|
+
include Frontend::Helper
|
7
|
+
|
8
|
+
delegate :object_name, :sort, to: :@table
|
9
|
+
|
10
|
+
def initialize(table, attribute, label: nil, **html_options)
|
11
|
+
super(**html_options)
|
12
|
+
|
13
|
+
@table = table
|
14
|
+
@attribute = attribute
|
15
|
+
@value = label
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
content = if @table.sort&.supports?(@table.collection, @attribute)
|
20
|
+
sort_link(value) # writes to html_options
|
21
|
+
else
|
22
|
+
value
|
23
|
+
end
|
24
|
+
|
25
|
+
tag.th(content, **@html_options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def value
|
29
|
+
if !@value.nil?
|
30
|
+
@value
|
31
|
+
elsif object_name.present?
|
32
|
+
translation
|
33
|
+
else
|
34
|
+
default_value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def translation(key = "activerecord.attributes.#{object_name}.#{@attribute}")
|
39
|
+
translate(key, default: default_value)
|
40
|
+
end
|
41
|
+
|
42
|
+
def default_value
|
43
|
+
@attribute.to_s.humanize.capitalize
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def sort_link(content)
|
49
|
+
(@html_options["data"] ||= {})["sort"] = sort.status(@attribute)
|
50
|
+
link_to(content, sort_url_for(sort: sort.toggle(@attribute)))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
class HeaderRowComponent < ViewComponent::Base # :nodoc:
|
6
|
+
include Frontend::Helper
|
7
|
+
|
8
|
+
renders_many :columns, ->(component) { component }
|
9
|
+
|
10
|
+
def initialize(table)
|
11
|
+
super()
|
12
|
+
|
13
|
+
@table = table
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
content # generate content before rendering
|
18
|
+
|
19
|
+
tag.tr(**@html_options) do
|
20
|
+
columns.each do |column|
|
21
|
+
concat(column.to_s)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def cell(attribute, **options, &block)
|
27
|
+
with_column(@table.class.header_cell_component.new(@table, attribute, **options), &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def header?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def body?
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -28,23 +28,23 @@ module Katalyst
|
|
28
28
|
end
|
29
29
|
|
30
30
|
included do
|
31
|
-
class_attribute :
|
31
|
+
class_attribute :_default_table_component, instance_accessor: false
|
32
32
|
end
|
33
33
|
|
34
34
|
class_methods do
|
35
|
-
# Set the table
|
35
|
+
# Set the table component to be used as the default for all tables
|
36
36
|
# in the views rendered by this controller and its subclasses.
|
37
37
|
#
|
38
38
|
# ==== Parameters
|
39
|
-
# * <tt>
|
40
|
-
def
|
41
|
-
self.
|
39
|
+
# * <tt>component</tt> - Default table component, an instance of +Katalyst::TableComponent+
|
40
|
+
def default_table_component(component)
|
41
|
+
self._default_table_component = component
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
# Default table
|
46
|
-
def
|
47
|
-
self.class.
|
45
|
+
# Default table component for this controller
|
46
|
+
def default_table_component
|
47
|
+
self.class._default_table_component
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
@@ -6,6 +6,18 @@ module Katalyst
|
|
6
6
|
module Helper # :nodoc:
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
|
+
def initialize(**options)
|
10
|
+
super()
|
11
|
+
|
12
|
+
options(**options)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Add HTML options to the current component.
|
16
|
+
def options(html: {}, **options)
|
17
|
+
@html_options = options.slice(:id, :aria, :class, :data).merge(html)
|
18
|
+
@html_options.stringify_keys!
|
19
|
+
end
|
20
|
+
|
9
21
|
# Generates a url for applying/toggling sort for the given column.
|
10
22
|
#
|
11
23
|
# @param sort [String, nil] sort parameter to apply, or nil to remove sorting
|
@@ -14,18 +26,15 @@ module Katalyst
|
|
14
26
|
# Implementation inspired by pagy's `pagy_url_for` helper.
|
15
27
|
# Preserve any existing GET parameters
|
16
28
|
# CAUTION: these parameters are not sanitised
|
17
|
-
params
|
29
|
+
params = if sort
|
30
|
+
request.GET.merge("sort" => sort).except("page")
|
31
|
+
else
|
32
|
+
request.GET.except("page", "sort")
|
33
|
+
end
|
18
34
|
query_string = params.empty? ? "" : "?#{Rack::Utils.build_nested_query(params)}"
|
19
35
|
|
20
36
|
"#{request.path}#{query_string}"
|
21
37
|
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def html_options_for_table_with(html: {}, **options)
|
26
|
-
html_options = options.slice(:id, :class, :data).merge(html)
|
27
|
-
html_options.stringify_keys!
|
28
|
-
end
|
29
38
|
end
|
30
39
|
end
|
31
40
|
end
|
@@ -1,51 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "frontend/builder/base"
|
4
|
-
require_relative "frontend/builder/body_cell"
|
5
|
-
require_relative "frontend/builder/body_row"
|
6
|
-
require_relative "frontend/builder/header_cell"
|
7
|
-
require_relative "frontend/builder/header_row"
|
8
3
|
require_relative "frontend/helper"
|
9
|
-
require_relative "frontend/table_builder"
|
10
4
|
|
11
5
|
module Katalyst
|
12
6
|
module Tables
|
13
7
|
# View Helper for generating HTML tables. Include in your ApplicationHelper, or similar.
|
14
8
|
module Frontend
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
table_options = options.slice(:header, :object_name, :sort)
|
19
|
-
|
20
|
-
table_options[:object_name] ||= collection.try(:model_name)&.i18n_key
|
21
|
-
|
22
|
-
html_options = html_options_for_table_with(**options)
|
23
|
-
|
24
|
-
builder = options.fetch(:builder) { default_table_builder_class }
|
25
|
-
builder.new(self, collection, table_options, html_options).build(&block)
|
26
|
-
end
|
27
|
-
|
28
|
-
def table_header_row(table, builder, &block)
|
29
|
-
builder.new(table).build(&block)
|
30
|
-
end
|
31
|
-
|
32
|
-
def table_header_cell(table, method, builder, **options, &block)
|
33
|
-
builder.new(table, method, **options).build(&block)
|
34
|
-
end
|
35
|
-
|
36
|
-
def table_body_row(table, object, builder, &block)
|
37
|
-
builder.new(table, object).build(&block)
|
38
|
-
end
|
39
|
-
|
40
|
-
def table_body_cell(table, object, method, builder, **options, &block)
|
41
|
-
builder.new(table, object, method, **options).build(&block)
|
9
|
+
def table_with(collection:, component: nil, **options, &block)
|
10
|
+
component ||= default_table_component_class
|
11
|
+
render(component.new(collection: collection, **options), &block)
|
42
12
|
end
|
43
13
|
|
44
14
|
private
|
45
15
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
16
|
+
def default_table_component_class
|
17
|
+
component = controller.try(:default_table_component) || TableComponent
|
18
|
+
component.respond_to?(:constantize) ? component.constantize : component
|
49
19
|
end
|
50
20
|
end
|
51
21
|
end
|
data/lib/katalyst/tables.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "view_component"
|
4
|
+
|
3
5
|
require_relative "tables/backend"
|
6
|
+
require_relative "tables/engine"
|
4
7
|
require_relative "tables/frontend"
|
5
8
|
require_relative "tables/version"
|
6
9
|
|
10
|
+
require_relative "tables/engine" if Object.const_defined?("Rails")
|
11
|
+
|
7
12
|
module Katalyst
|
8
13
|
module Tables
|
9
14
|
class Error < StandardError; end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: katalyst-tables
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katalyst Interactive
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-07-
|
12
|
-
dependencies:
|
11
|
+
date: 2023-07-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: view_component
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
description: Builder-style HTML table generator for building tabular index views.
|
14
28
|
Supports sorting by columns.
|
15
29
|
email:
|
@@ -21,17 +35,17 @@ files:
|
|
21
35
|
- CHANGELOG.md
|
22
36
|
- LICENSE.txt
|
23
37
|
- README.md
|
38
|
+
- app/components/katalyst/table_component.rb
|
39
|
+
- app/components/katalyst/tables/body_cell_component.rb
|
40
|
+
- app/components/katalyst/tables/body_row_component.rb
|
41
|
+
- app/components/katalyst/tables/header_cell_component.rb
|
42
|
+
- app/components/katalyst/tables/header_row_component.rb
|
24
43
|
- lib/katalyst/tables.rb
|
25
44
|
- lib/katalyst/tables/backend.rb
|
26
45
|
- lib/katalyst/tables/backend/sort_form.rb
|
46
|
+
- lib/katalyst/tables/engine.rb
|
27
47
|
- lib/katalyst/tables/frontend.rb
|
28
|
-
- lib/katalyst/tables/frontend/builder/base.rb
|
29
|
-
- lib/katalyst/tables/frontend/builder/body_cell.rb
|
30
|
-
- lib/katalyst/tables/frontend/builder/body_row.rb
|
31
|
-
- lib/katalyst/tables/frontend/builder/header_cell.rb
|
32
|
-
- lib/katalyst/tables/frontend/builder/header_row.rb
|
33
48
|
- lib/katalyst/tables/frontend/helper.rb
|
34
|
-
- lib/katalyst/tables/frontend/table_builder.rb
|
35
49
|
- lib/katalyst/tables/version.rb
|
36
50
|
homepage: https://github.com/katalyst/katalyst-tables
|
37
51
|
licenses:
|
@@ -1,63 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_support"
|
4
|
-
require "active_support/core_ext/module/delegation"
|
5
|
-
|
6
|
-
require_relative "../helper"
|
7
|
-
|
8
|
-
module Katalyst
|
9
|
-
module Tables
|
10
|
-
module Frontend
|
11
|
-
module Builder
|
12
|
-
class Base # :nodoc:
|
13
|
-
include Helper
|
14
|
-
|
15
|
-
attr_reader :table
|
16
|
-
|
17
|
-
delegate :sort,
|
18
|
-
:table_header_cell,
|
19
|
-
:table_header_row,
|
20
|
-
:table_body_cell,
|
21
|
-
:table_body_row,
|
22
|
-
:template,
|
23
|
-
to: :table
|
24
|
-
|
25
|
-
delegate :content_tag,
|
26
|
-
:link_to,
|
27
|
-
:render,
|
28
|
-
:request,
|
29
|
-
:translate,
|
30
|
-
:with_output_buffer,
|
31
|
-
to: :template
|
32
|
-
|
33
|
-
def initialize(table, **options)
|
34
|
-
@table = table
|
35
|
-
@header = false
|
36
|
-
self.options(**options)
|
37
|
-
end
|
38
|
-
|
39
|
-
def header?
|
40
|
-
@header
|
41
|
-
end
|
42
|
-
|
43
|
-
def body?
|
44
|
-
!@header
|
45
|
-
end
|
46
|
-
|
47
|
-
def options(**options)
|
48
|
-
@html_options = html_options_for_table_with(**options)
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def table_tag(type, value = nil, &block)
|
54
|
-
# capture output before calling tag, to allow users to modify `options` during body execution
|
55
|
-
value = with_output_buffer(&block) if block_given?
|
56
|
-
|
57
|
-
content_tag(type, value, @html_options, &block)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "base"
|
4
|
-
|
5
|
-
module Katalyst
|
6
|
-
module Tables
|
7
|
-
module Frontend
|
8
|
-
module Builder
|
9
|
-
class BodyCell < Base # :nodoc:
|
10
|
-
attr_reader :object, :method
|
11
|
-
|
12
|
-
def initialize(table, object, method, **options)
|
13
|
-
super table, **options
|
14
|
-
|
15
|
-
@type = options.fetch(:heading, false) ? :th : :td
|
16
|
-
@object = object
|
17
|
-
@method = method
|
18
|
-
end
|
19
|
-
|
20
|
-
def build
|
21
|
-
table_tag(@type) { block_given? ? yield(self).to_s : value.to_s }
|
22
|
-
end
|
23
|
-
|
24
|
-
def value
|
25
|
-
object.public_send(method)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "base"
|
4
|
-
|
5
|
-
module Katalyst
|
6
|
-
module Tables
|
7
|
-
module Frontend
|
8
|
-
module Builder
|
9
|
-
class BodyRow < Base # :nodoc:
|
10
|
-
attr_reader :object
|
11
|
-
|
12
|
-
def initialize(table, object)
|
13
|
-
super table
|
14
|
-
|
15
|
-
@object = object
|
16
|
-
end
|
17
|
-
|
18
|
-
def build
|
19
|
-
table_tag(:tr) { yield self, object }
|
20
|
-
end
|
21
|
-
|
22
|
-
def cell(method, **options, &block)
|
23
|
-
table_body_cell(object, method, **options, &block)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "body_cell"
|
4
|
-
|
5
|
-
module Katalyst
|
6
|
-
module Tables
|
7
|
-
module Frontend
|
8
|
-
module Builder
|
9
|
-
class HeaderCell < BodyCell # :nodoc:
|
10
|
-
def initialize(table, method, **options)
|
11
|
-
super(table, nil, method, **options)
|
12
|
-
|
13
|
-
@value = options[:label]
|
14
|
-
@header = true
|
15
|
-
end
|
16
|
-
|
17
|
-
def build(&_block)
|
18
|
-
# NOTE: block ignored intentionally but subclasses may consume it
|
19
|
-
if @table.sort&.supports?(@table.collection, method)
|
20
|
-
content = sort_link(value) # writes to html_options
|
21
|
-
table_tag :th, content # consumes options
|
22
|
-
else
|
23
|
-
table_tag :th, value
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def value
|
28
|
-
if !@value.nil?
|
29
|
-
@value
|
30
|
-
elsif @table.object_name.present?
|
31
|
-
translation
|
32
|
-
else
|
33
|
-
default_value
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def translation(key = "activerecord.attributes.#{@table.object_name}.#{method}")
|
38
|
-
translate(key, default: default_value)
|
39
|
-
end
|
40
|
-
|
41
|
-
def default_value
|
42
|
-
method.to_s.humanize.titleize
|
43
|
-
end
|
44
|
-
|
45
|
-
private
|
46
|
-
|
47
|
-
def sort_link(content)
|
48
|
-
(@html_options["data"] ||= {})["sort"] = sort.status(method)
|
49
|
-
link_to(content, sort_url_for(sort: sort.toggle(method)))
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "body_row"
|
4
|
-
|
5
|
-
module Katalyst
|
6
|
-
module Tables
|
7
|
-
module Frontend
|
8
|
-
module Builder
|
9
|
-
class HeaderRow < BodyRow # :nodoc:
|
10
|
-
def initialize(table)
|
11
|
-
super table, nil
|
12
|
-
|
13
|
-
@header = true
|
14
|
-
end
|
15
|
-
|
16
|
-
def cell(method, **options, &block)
|
17
|
-
table_header_cell(method, **options, &block)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,71 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "builder/body_cell"
|
4
|
-
require_relative "builder/body_row"
|
5
|
-
require_relative "builder/header_cell"
|
6
|
-
require_relative "builder/header_row"
|
7
|
-
|
8
|
-
module Katalyst
|
9
|
-
module Tables
|
10
|
-
module Frontend
|
11
|
-
# Builder API for generating HTML tables from ActiveRecord.
|
12
|
-
# @see Frontend#table_with
|
13
|
-
class TableBuilder
|
14
|
-
attr_reader :template, :collection, :object_name, :sort
|
15
|
-
|
16
|
-
def initialize(template, collection, options, html_options)
|
17
|
-
@template = template
|
18
|
-
@collection = collection
|
19
|
-
@header = options.fetch(:header, true)
|
20
|
-
@object_name = options.fetch(:object_name, nil)
|
21
|
-
@sort = options[:sort]
|
22
|
-
@html_options = html_options
|
23
|
-
end
|
24
|
-
|
25
|
-
def table_header_row(builder = nil, &block)
|
26
|
-
@template.table_header_row(self, builder || Builder::HeaderRow, &block)
|
27
|
-
end
|
28
|
-
|
29
|
-
def table_header_cell(method, builder = nil, **options, &block)
|
30
|
-
@template.table_header_cell(self, method, builder || Builder::HeaderCell, **options, &block)
|
31
|
-
end
|
32
|
-
|
33
|
-
def table_body_row(object, builder = nil, &block)
|
34
|
-
@template.table_body_row(self, object, builder || Builder::BodyRow, &block)
|
35
|
-
end
|
36
|
-
|
37
|
-
def table_body_cell(object, method, builder = nil, **options, &block)
|
38
|
-
@template.table_body_cell(self, object, method, builder || Builder::BodyCell, **options, &block)
|
39
|
-
end
|
40
|
-
|
41
|
-
def build(&block)
|
42
|
-
template.content_tag("table", @html_options) do
|
43
|
-
thead(&block) + tbody(&block)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def thead(&block)
|
50
|
-
return "".html_safe unless @header
|
51
|
-
|
52
|
-
template.content_tag("thead") do
|
53
|
-
table_header_row(&block)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def tbody(&block)
|
58
|
-
template.content_tag("tbody") do
|
59
|
-
buffer = ActiveSupport::SafeBuffer.new
|
60
|
-
|
61
|
-
collection.each do |object|
|
62
|
-
buffer << table_body_row(object, &block)
|
63
|
-
end
|
64
|
-
|
65
|
-
buffer
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|