katalyst-tables 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +71 -0
- data/app/models/concerns/katalyst/tables/collection/pagination.rb +66 -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 +33 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a572503d453b3b8b98a82942a48c5e2772cec4023417e8db71ed2cd8299331a
|
4
|
+
data.tar.gz: 4761c4fa4b21aa8c91f70c2118e27bd68dfd700ffc419ed6f7eae299b582710f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa9c1dc28479dc744c18804999d5f34ea9937e1500dbe8c01a1f2c183439077dfdd7047dbb8ce2ea0f93ef43bcfcea15eee6d08b2606f3682a00747e590cbc3b
|
7
|
+
data.tar.gz: 45c36c312e7f6efa8b11409d4822d135c961eba6ade51055b4671371b3b1a6e25a01c9fd1e059c0a89fa5da6bea3e7fe56fda267a1b3d7bfdf51e515b7b8a696
|
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
|