katalyst-tables 2.0.0 → 2.1.1
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 +15 -0
- data/README.md +151 -103
- data/app/assets/config/katalyst-tables.js +1 -0
- data/app/assets/javascripts/controllers/tables/turbo_collection_controller.js +22 -0
- data/app/components/concerns/katalyst/tables/configurable_component.rb +31 -0
- data/app/components/concerns/katalyst/tables/has_html_attributes.rb +45 -0
- data/app/components/concerns/katalyst/tables/has_table_content.rb +33 -0
- data/app/components/concerns/katalyst/tables/sortable.rb +32 -0
- data/app/components/concerns/katalyst/tables/turbo_replaceable.rb +62 -0
- data/app/components/katalyst/table_component.rb +51 -40
- data/app/components/katalyst/tables/body_cell_component.rb +4 -4
- data/app/components/katalyst/tables/body_row_component.rb +3 -3
- data/app/components/katalyst/tables/empty_caption_component.html.erb +6 -0
- data/app/components/katalyst/tables/empty_caption_component.rb +38 -0
- data/app/components/katalyst/tables/header_cell_component.rb +20 -16
- data/app/components/katalyst/tables/header_row_component.rb +6 -5
- data/app/components/katalyst/tables/pagy_nav_component.rb +26 -0
- data/app/components/katalyst/turbo/pagy_nav_component.rb +23 -0
- data/app/components/katalyst/turbo/table_component.rb +48 -0
- data/app/models/concerns/katalyst/tables/collection/core.rb +67 -0
- data/app/models/concerns/katalyst/tables/collection/pagination.rb +66 -0
- data/app/models/concerns/katalyst/tables/collection/reducers.rb +67 -0
- data/app/models/concerns/katalyst/tables/collection/sorting.rb +63 -0
- data/app/models/katalyst/tables/collection.rb +32 -0
- data/config/importmap.rb +7 -0
- data/lib/katalyst/tables/backend/sort_form.rb +16 -2
- data/lib/katalyst/tables/engine.rb +13 -0
- data/lib/katalyst/tables/frontend/helper.rb +4 -14
- data/lib/katalyst/tables/version.rb +1 -1
- metadata +34 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c5cc96bc55db7f0cf4c3a22659e6054607640e6d33af939805d4be6ca8f25c1
|
4
|
+
data.tar.gz: 1661c91c498d7c994afca1cb4a0bfcb69b6e2076a419ae71b138c5ae086e4a0d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6f94b4a381ec3f9e952d594e1525b61b97d17d2d8c42fcc230fa81328303768d174f2778eb3918f681f28d38227cf9f3c83af340e0ea5ecc78857c6dad7c44d
|
7
|
+
data.tar.gz: 01a7fcf97aea5435986ab2b0357249bfd03bb60bc98fb22769aef88c154520753e9b340fe981edcf74f6bf4d32eafbd45bd318158cf92926fe3162f459928ff7
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [2.1.0]
|
4
|
+
|
5
|
+
- Add Collection model for building collections in a controller from params.
|
6
|
+
- See [[README.md]] for examples
|
7
|
+
- Add turbo entry points for table and pagy_nav
|
8
|
+
- See [[README.md]] for examples
|
9
|
+
- Add support for row partials when content is not provided
|
10
|
+
- See [[README.md]] for examples
|
11
|
+
- Add messages when table is empty, off by default (caption: true)
|
12
|
+
- Add PagyNavComponent for rendering `pagy_nav` from a collection.
|
13
|
+
- Replaces internal references to SortForm to use `sorting` instead
|
14
|
+
- No changes required to existing code unless you were using the internal
|
15
|
+
classes directly
|
16
|
+
- Change allows sort param and sorting model to co-exist
|
17
|
+
|
3
18
|
## [2.0.0]
|
4
19
|
|
5
20
|
- Replaces builders with view_components
|
data/README.md
CHANGED
@@ -16,76 +16,99 @@ And then execute:
|
|
16
16
|
|
17
17
|
## Usage
|
18
18
|
|
19
|
-
This gem provides
|
20
|
-
|
19
|
+
This gem provides entry points for backend and frontend concerns:
|
20
|
+
* `Katalyst::TableComponent` can be used render encapsulated tables, it calls a
|
21
|
+
partial for each row.
|
22
|
+
* `Katalyst::Tables::Frontend` provides `table_with` for inline table generation
|
23
|
+
* `Katalyst::Tables::Collection::Base` provides a default entry point for
|
24
|
+
building collections in your controller actions.
|
21
25
|
|
22
|
-
|
26
|
+
## Frontend
|
23
27
|
|
24
|
-
|
28
|
+
Use `Katalyst::TableComponent` to build a table component from an ActiveRecord
|
29
|
+
collection, or from a `Katalyst::Tables::Collection::Base` instance.
|
30
|
+
|
31
|
+
For example, if you render `Katalyst::TableComponent.new(collection: @people)`,
|
32
|
+
the table component will look for a partial called `_person.html+row.erb` and
|
33
|
+
render it for each row (and once for the header row).
|
25
34
|
|
26
35
|
```erb
|
27
|
-
|
28
|
-
|
29
|
-
<%=
|
30
|
-
<%= row.cell :actions do %>
|
31
|
-
<%= link_to "Edit", person %>
|
32
|
-
<% end %>
|
36
|
+
<%# locals: { row:, person: nil } %>
|
37
|
+
<% row.cell :name do |cell| %>
|
38
|
+
<%= link_to cell.value, [:edit, person] %>
|
33
39
|
<% end %>
|
40
|
+
<% row.cell :email %>
|
34
41
|
```
|
35
42
|
|
36
|
-
The table
|
43
|
+
The table component will call your partial once per row and accumulate the cells
|
44
|
+
you generate into rows, including a header row:
|
37
45
|
|
38
46
|
```html
|
39
47
|
|
40
48
|
<table>
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
<
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
</
|
54
|
-
<
|
55
|
-
|
56
|
-
|
57
|
-
<td><a href="/people/2/edit">Edit</a></td>
|
58
|
-
</tr>
|
59
|
-
</tbody>
|
49
|
+
<thead>
|
50
|
+
<tr>
|
51
|
+
<th>Name</th>
|
52
|
+
<th>Email</th>
|
53
|
+
</tr>
|
54
|
+
</thead>
|
55
|
+
<tbody>
|
56
|
+
<tr>
|
57
|
+
<td><a href="/people/1/edit">Alice</a></td>
|
58
|
+
<td>alice@acme.org</td>
|
59
|
+
</tr>
|
60
|
+
<tr>
|
61
|
+
<td><a href="/people/2/edit">Bob</a></td>
|
62
|
+
<td>bob@acme.org</td>
|
63
|
+
</tr>
|
64
|
+
</tbody>
|
60
65
|
</table>
|
61
66
|
```
|
62
67
|
|
63
|
-
|
68
|
+
You can customize the partial and/or the name of the resource in a similar style
|
69
|
+
to view partials:
|
64
70
|
|
65
|
-
|
71
|
+
```erb
|
72
|
+
<%= render Katalyst::TableComponent.new(collection: @employees, as: :person, partial: "person") %>
|
73
|
+
```
|
74
|
+
|
75
|
+
### Inline tables
|
66
76
|
|
67
|
-
|
77
|
+
You can use the `table_with` helper to generate a table inline in your view without explicitly interacting with the
|
78
|
+
table component. This is primarily intended for backwards compatibility, but it can be useful for simple tables.
|
79
|
+
|
80
|
+
Add `include Katalyst::Tables::Frontend` to your `ApplicationHelper` or similar.
|
68
81
|
|
69
82
|
```erb
|
70
|
-
<%= table_with collection: @people
|
71
|
-
|
83
|
+
<%= table_with collection: @people do |row, person| %>
|
84
|
+
<% row.cell :name do |cell| %>
|
85
|
+
<%= link_to cell.value, [:edit, person] %>
|
86
|
+
<% end %>
|
87
|
+
<% row.cell :email %>
|
72
88
|
<% end %>
|
73
89
|
```
|
74
90
|
|
91
|
+
### HTML Attributes
|
92
|
+
|
93
|
+
You can add custom attributes on table, row, and cell tags.
|
94
|
+
|
95
|
+
The table tag takes attributes passed to `TableComponent` or via the call to `table_with`, similar to `form_with`:
|
96
|
+
|
97
|
+
```erb
|
98
|
+
<%= TableComponent.new(collection: @people, id: "people-table")
|
99
|
+
```
|
100
|
+
|
75
101
|
Cells support the same approach:
|
76
102
|
|
77
103
|
```erb
|
78
104
|
<%= row.cell :name, class: "name" %>
|
79
105
|
```
|
80
106
|
|
81
|
-
Rows do not get called directly, so instead you can
|
107
|
+
Rows do not get called directly, so instead you can assign to `html_attributes` on the row builder to customize row tag
|
82
108
|
generation.
|
83
109
|
|
84
110
|
```erb
|
85
|
-
|
86
|
-
<% row.options data: { id: person.id } if row.body? %>
|
87
|
-
...
|
88
|
-
<% end %>
|
111
|
+
<% row.html_attributes = { id: person.id } if row.body? %>
|
89
112
|
```
|
90
113
|
|
91
114
|
Note: because the row builder gets called to generate the header row, you may need to guard calls that access the
|
@@ -93,8 +116,8 @@ Note: because the row builder gets called to generate the header row, you may ne
|
|
93
116
|
|
94
117
|
#### Headers
|
95
118
|
|
96
|
-
|
97
|
-
|
119
|
+
Tables will automatically generate a header row for you by calling your row partial or provided block with no object.
|
120
|
+
During this call, `row.header?` is true, `row.body?` is false, and the object (`person`) is nil.
|
98
121
|
|
99
122
|
All cells generated in the table header iteration will automatically be header cells, but you can also make header cells
|
100
123
|
in your body rows by passing `heading: true` when you generate the cell.
|
@@ -130,8 +153,8 @@ the table cell. This is often all you need to do, but if you do want to customis
|
|
130
153
|
the value you can pass a block instead:
|
131
154
|
|
132
155
|
```erb
|
133
|
-
|
134
|
-
|
156
|
+
<% row.cell :status do %>
|
157
|
+
<%= person.password.present? ? "Active" : "Invited" %>
|
135
158
|
<% end %>
|
136
159
|
```
|
137
160
|
|
@@ -139,20 +162,61 @@ In the context of the block you have access the cell builder if you simply
|
|
139
162
|
want to extend the default behaviour:
|
140
163
|
|
141
164
|
```erb
|
142
|
-
|
143
|
-
|
165
|
+
<% row.cell :status do |cell| %>
|
166
|
+
<%= link_to cell.value, person %>
|
144
167
|
<% end %>
|
145
168
|
```
|
146
169
|
|
147
|
-
You can also
|
148
|
-
please note that this will replace any options passed to the cell
|
170
|
+
You can also assign to `html_attributes` on the cell builder, similar to the row
|
171
|
+
builder, but please note that this will replace any options passed to the cell
|
172
|
+
as arguments.
|
149
173
|
|
150
|
-
|
174
|
+
## Collections
|
151
175
|
|
152
|
-
The
|
153
|
-
|
176
|
+
The `Katalyst::Tables::Collection::Base` class provides a convenient way to
|
177
|
+
manage collections in your controller actions. It is designed to be used with
|
178
|
+
Pagy for pagination and provides built-in sorting when used with ActiveRecord
|
179
|
+
collections. Sorting and Pagination are off by default, but you can create
|
180
|
+
a custom `ApplicationCollection` class that sets them on by default.
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
class ApplicationCollection < Katalyst::Tables::Collection::Base
|
184
|
+
config.sorting = "name" # requires models have a name attribute
|
185
|
+
config.pagination = true
|
186
|
+
end
|
187
|
+
```
|
154
188
|
|
155
|
-
|
189
|
+
You can then use this class in your controller actions:
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
class PeopleController < ApplicationController
|
193
|
+
def index
|
194
|
+
@people = ApplicationCollection.new.with_params(params).apply(People.all)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
```
|
198
|
+
|
199
|
+
Collections can be passed directly to `TableComponent` and it will automatically
|
200
|
+
detect features such as sorting and generate the appropriate table header links.
|
201
|
+
|
202
|
+
```erb
|
203
|
+
<%= render TableComponent.new(collection: @people) %>
|
204
|
+
```
|
205
|
+
|
206
|
+
## Sort
|
207
|
+
|
208
|
+
When sort is enabled, table columns will be automatically sortable in the
|
209
|
+
frontend for any column that corresponds to an attribute on the model. You can
|
210
|
+
also add sorting to non-attribute columns by defining a scope in your
|
211
|
+
model:
|
212
|
+
|
213
|
+
```
|
214
|
+
scope :order_by_status, ->(direction) { ... }
|
215
|
+
```
|
216
|
+
|
217
|
+
You can also use sort without using collections, this was the primary backend
|
218
|
+
interface for V1 and takes design cues from Pagy. Start by including the backend
|
219
|
+
in your controller(s):
|
156
220
|
|
157
221
|
```ruby
|
158
222
|
include Katalyst::Tables::Backend
|
@@ -183,55 +247,47 @@ links and show the current sort state:
|
|
183
247
|
<%= table_with collection: @people, sort: @sort do |row, person| %>
|
184
248
|
<%= row.cell :name %>
|
185
249
|
<%= row.cell :email %>
|
186
|
-
<%= row.cell :actions do %>
|
187
|
-
<%= link_to "Edit", person %>
|
188
|
-
<% end %>
|
189
250
|
<% end %>
|
190
251
|
```
|
191
252
|
|
192
|
-
|
193
|
-
automatically sortable in the frontend.
|
253
|
+
## Pagination
|
194
254
|
|
195
|
-
|
196
|
-
model:
|
255
|
+
This gem designed to work with [pagy](https://github.com/ddnexus/pagy/).
|
197
256
|
|
198
|
-
|
199
|
-
|
200
|
-
```
|
257
|
+
If you use collections and enable pagination then pagy will be called internally
|
258
|
+
and the pagy metadata will be available as `pagination` on the collection.
|
201
259
|
|
202
|
-
|
203
|
-
|
204
|
-
you want to provide a tie-breaker default ordering, the best way to do so is after
|
205
|
-
calling `table_sort`.
|
260
|
+
`Katalyst::Tables::PagyNavComponent` can be used to render the pagination links
|
261
|
+
for a collection.
|
206
262
|
|
207
|
-
|
263
|
+
```erb
|
264
|
+
<%= render Katalyst::Tables::PagyNavComponent.new(collection: @people) %>
|
265
|
+
```
|
208
266
|
|
209
|
-
|
267
|
+
## Turbo streams
|
210
268
|
|
211
|
-
This gem
|
269
|
+
This gem provides turbo stream entry points for table and pagy_nav. These are
|
270
|
+
identical in the options they support, but they require ids, and they will
|
271
|
+
automatically render turbo stream replace tags when rendered as part of a turbo
|
272
|
+
stream response.
|
212
273
|
|
213
|
-
|
274
|
+
To take full advantage of this feature, we suggest you build the component in
|
275
|
+
your controller and pass it to the view. This allows you to use the same
|
276
|
+
controller for both HTML and turbo responses.
|
214
277
|
|
278
|
+
```ruby
|
215
279
|
def index
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
280
|
+
collection = ApplicationCollection.new.with_params(params).apply(People.all)
|
281
|
+
table = Katalyst::Turbo::TableComponent.new(collection:, id: "people")
|
282
|
+
|
283
|
+
respond_to do |format|
|
284
|
+
format.html { render locals: { table: table } }
|
285
|
+
format.turbo_stream { render table }
|
286
|
+
end
|
220
287
|
end
|
221
288
|
```
|
222
289
|
|
223
|
-
|
224
|
-
<%= table_with collection: @people, sort: @sort do |row, person| %>
|
225
|
-
<%= row.cell :name %>
|
226
|
-
<%= row.cell :email %>
|
227
|
-
<%= row.cell :actions do %>
|
228
|
-
<%= link_to "Edit", person %>
|
229
|
-
<% end %>
|
230
|
-
<% end %>
|
231
|
-
<%== pagy_nav(@pagy) %>
|
232
|
-
```
|
233
|
-
|
234
|
-
### Customization
|
290
|
+
## Customization
|
235
291
|
|
236
292
|
A common pattern we use is to have a cell at the end of the table for actions. For example:
|
237
293
|
|
@@ -255,11 +311,12 @@ A common pattern we use is to have a cell at the end of the table for actions. F
|
|
255
311
|
</table>
|
256
312
|
```
|
257
313
|
|
258
|
-
You can write a custom
|
259
|
-
|
314
|
+
You can write a custom component that helps generate this type of table by
|
315
|
+
adding the required classes and adding helpers for generating the actions.
|
316
|
+
This allows for a declarative table syntax, something like this:
|
260
317
|
|
261
318
|
```erb
|
262
|
-
<%=
|
319
|
+
<%= render ActionTableComponent.new(collection:) do |row| %>
|
263
320
|
<% row.cell :name %>
|
264
321
|
<% row.actions do |cell| %>
|
265
322
|
<%= cell.action "Edit", :edit %>
|
@@ -276,10 +333,9 @@ class ActionTableComponent < Katalyst::TableComponent
|
|
276
333
|
config.header_row = "ActionHeaderRow"
|
277
334
|
config.body_row = "ActionBodyRow"
|
278
335
|
config.body_cell = "ActionBodyCell"
|
279
|
-
|
280
|
-
def
|
281
|
-
|
282
|
-
super
|
336
|
+
|
337
|
+
def default_attributes
|
338
|
+
{ class: "action-table" }
|
283
339
|
end
|
284
340
|
|
285
341
|
class ActionHeaderRow < Katalyst::Tables::HeaderRowComponent
|
@@ -295,21 +351,13 @@ class ActionTableComponent < Katalyst::TableComponent
|
|
295
351
|
end
|
296
352
|
|
297
353
|
class ActionBodyCell < Katalyst::Tables::BodyCellComponent
|
298
|
-
def action(label, href, **
|
299
|
-
content_tag
|
354
|
+
def action(label, href, **attrs)
|
355
|
+
content_tag(:a, label, href: href, **attrs)
|
300
356
|
end
|
301
357
|
end
|
302
358
|
end
|
303
359
|
```
|
304
360
|
|
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:
|
306
|
-
|
307
|
-
```html
|
308
|
-
class ApplicationController < ActiveController::Base
|
309
|
-
default_table_component ActionTableComponent
|
310
|
-
end
|
311
|
-
```
|
312
|
-
|
313
361
|
## Development
|
314
362
|
|
315
363
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests.
|
@@ -0,0 +1 @@
|
|
1
|
+
//= link_tree ../javascripts
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
|
+
|
3
|
+
export default class TurboCollectionController extends Controller {
|
4
|
+
static values = {
|
5
|
+
url: String,
|
6
|
+
sort: String,
|
7
|
+
}
|
8
|
+
|
9
|
+
urlValueChanged(url) {
|
10
|
+
window.history.replaceState({}, "", this.urlValue);
|
11
|
+
}
|
12
|
+
|
13
|
+
sortValueChanged(sort) {
|
14
|
+
document.querySelectorAll(this.#sortSelector).forEach((input) => {
|
15
|
+
if (input) input.value = sort;
|
16
|
+
});
|
17
|
+
}
|
18
|
+
|
19
|
+
get #sortSelector() {
|
20
|
+
return "input[name='sort']";
|
21
|
+
}
|
22
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module ConfigurableComponent # :nodoc:
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
include ActiveSupport::Configurable
|
9
|
+
|
10
|
+
included do
|
11
|
+
# Workaround: ViewComponent::Base.config is incompatible with ActiveSupport::Configurable
|
12
|
+
@_config = Class.new(ActiveSupport::Configurable::Configuration).new
|
13
|
+
end
|
14
|
+
|
15
|
+
class_methods do
|
16
|
+
# Define a configurable sub-component.
|
17
|
+
def config_component(name, component_name: "#{name}_component", default: nil)
|
18
|
+
config_accessor(name)
|
19
|
+
config.public_send("#{name}=", default)
|
20
|
+
define_method(component_name) do
|
21
|
+
instance_variable_get("@#{component_name}") if instance_variable_defined?("@#{component_name}")
|
22
|
+
|
23
|
+
klass = config.public_send(name)
|
24
|
+
component = self.class.const_get(klass) if klass
|
25
|
+
instance_variable_set("@#{component_name}", component) if component
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "html_attributes_utils"
|
4
|
+
|
5
|
+
module Katalyst
|
6
|
+
module Tables
|
7
|
+
module HasHtmlAttributes # :nodoc:
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
using HTMLAttributesUtils
|
11
|
+
|
12
|
+
DEFAULT_MERGEABLE_ATTRIBUTES = [
|
13
|
+
*HTMLAttributesUtils::DEFAULT_MERGEABLE_ATTRIBUTES,
|
14
|
+
%i[data controller],
|
15
|
+
%i[data action]
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
def initialize(**options)
|
19
|
+
super(**options.except(:id, :aria, :class, :data, :html))
|
20
|
+
|
21
|
+
self.html_attributes = options
|
22
|
+
end
|
23
|
+
|
24
|
+
# Add HTML options to the current component.
|
25
|
+
# Public method for customizing components from within
|
26
|
+
def html_attributes=(options)
|
27
|
+
@html_attributes = options.slice(:id, :aria, :class, :data).merge(options.fetch(:html, {}))
|
28
|
+
end
|
29
|
+
|
30
|
+
# Backwards compatibility with tables 1.0
|
31
|
+
alias options html_attributes=
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def html_attributes
|
36
|
+
default_attributes
|
37
|
+
.deep_merge_html_attributes(@html_attributes, mergeable_attributes: DEFAULT_MERGEABLE_ATTRIBUTES)
|
38
|
+
end
|
39
|
+
|
40
|
+
def default_attributes
|
41
|
+
{}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module HasTableContent # :nodoc:
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def initialize(object_name: nil, partial: nil, as: nil, **options)
|
9
|
+
super(**options)
|
10
|
+
|
11
|
+
@object_name = object_name || model_name&.i18n_key
|
12
|
+
@partial = partial
|
13
|
+
@as = as
|
14
|
+
end
|
15
|
+
|
16
|
+
def model_name
|
17
|
+
collection.model_name if collection.respond_to?(:model_name)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def row_proc
|
23
|
+
@row_proc ||= @__vc_render_in_block || method(:row_partial)
|
24
|
+
end
|
25
|
+
|
26
|
+
def row_partial(row, record = nil)
|
27
|
+
partial = @partial || model_name&.param_key&.to_s
|
28
|
+
as = @as || model_name&.param_key&.to_sym
|
29
|
+
render(partial: partial, variants: [:row], locals: { as => record, row: row })
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
# Extension to add sorting support to a collection.
|
6
|
+
# Assumes collection and sorting are available in the current scope.
|
7
|
+
module Sortable
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
# Returns true when the given attribute is sortable.
|
11
|
+
def sortable?(attribute)
|
12
|
+
sorting&.supports?(collection, attribute)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Generates a url for applying/toggling sort for the given column.
|
16
|
+
def sort_url(attribute) # rubocop:disable Metrics/AbcSize
|
17
|
+
# Implementation inspired by pagy's `pagy_url_for` helper.
|
18
|
+
# Preserve any existing GET parameters
|
19
|
+
# CAUTION: these parameters are not sanitised
|
20
|
+
sort = attribute && sorting.toggle(attribute)
|
21
|
+
params = if sort && !sort.eql?(sorting.default)
|
22
|
+
request.GET.merge("sort" => sort).except("page")
|
23
|
+
else
|
24
|
+
request.GET.except("page", "sort")
|
25
|
+
end
|
26
|
+
query_string = params.empty? ? "" : "?#{Rack::Utils.build_nested_query(params)}"
|
27
|
+
|
28
|
+
"#{request.path}#{query_string}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
# Adds support for turbo stream replacement to ViewComponents. Components
|
6
|
+
# that are rendered from a turbo-stream-compatible response will be rendered
|
7
|
+
# using turbo stream replacement. Components must define `id`.
|
8
|
+
#
|
9
|
+
# Turbo stream replacement rendering will only be enabled if the component
|
10
|
+
# passes `turbo: true` as a constructor option.
|
11
|
+
module TurboReplaceable
|
12
|
+
extend ActiveSupport::Concern
|
13
|
+
|
14
|
+
include ::Turbo::StreamsHelper
|
15
|
+
|
16
|
+
def turbo?
|
17
|
+
@turbo
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(turbo: true, **options)
|
21
|
+
super(**options)
|
22
|
+
|
23
|
+
@turbo = turbo
|
24
|
+
end
|
25
|
+
|
26
|
+
class_methods do
|
27
|
+
# Redefine the compiler to use our custom compiler.
|
28
|
+
# Compiler is set on `inherited` so we need to re-set it if it's not the expected type.
|
29
|
+
def compiler
|
30
|
+
@vc_compiler = @vc_compiler.is_a?(TurboCompiler) ? @vc_compiler : TurboCompiler.new(self)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
included do
|
35
|
+
# ensure that our custom compiler is used, as `inherited` calls `compile` before our module is included.
|
36
|
+
compile(force: true) if compiled?
|
37
|
+
end
|
38
|
+
|
39
|
+
# Wraps the default compiler provided by ViewComponent to add turbo support.
|
40
|
+
class TurboCompiler < ViewComponent::Compiler
|
41
|
+
private
|
42
|
+
|
43
|
+
def define_render_template_for # rubocop:disable Metrics/MethodLength
|
44
|
+
super
|
45
|
+
|
46
|
+
redefinition_lock.synchronize do
|
47
|
+
component_class.alias_method(:vc_render_template_for, :render_template_for)
|
48
|
+
component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
49
|
+
def render_template_for(variant = nil)
|
50
|
+
return vc_render_template_for(variant) unless turbo?
|
51
|
+
controller.respond_to do |format|
|
52
|
+
format.html { vc_render_template_for(variant) }
|
53
|
+
format.turbo_stream { turbo_stream.replace(id, vc_render_template_for(variant)) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
RUBY
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|