katalyst-tables 1.1.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/README.md +166 -134
  4. data/app/assets/config/katalyst-tables.js +1 -0
  5. data/app/assets/javascripts/controllers/tables/turbo_collection_controller.js +22 -0
  6. data/app/components/concerns/katalyst/tables/configurable_component.rb +31 -0
  7. data/app/components/concerns/katalyst/tables/has_html_attributes.rb +45 -0
  8. data/app/components/concerns/katalyst/tables/has_table_content.rb +33 -0
  9. data/app/components/concerns/katalyst/tables/sortable.rb +32 -0
  10. data/app/components/concerns/katalyst/tables/turbo_replaceable.rb +62 -0
  11. data/app/components/katalyst/table_component.rb +102 -0
  12. data/app/components/katalyst/tables/body_cell_component.rb +40 -0
  13. data/app/components/katalyst/tables/body_row_component.rb +40 -0
  14. data/app/components/katalyst/tables/empty_caption_component.html.erb +6 -0
  15. data/app/components/katalyst/tables/empty_caption_component.rb +38 -0
  16. data/app/components/katalyst/tables/header_cell_component.rb +58 -0
  17. data/app/components/katalyst/tables/header_row_component.rb +40 -0
  18. data/app/components/katalyst/tables/pagy_nav_component.rb +26 -0
  19. data/app/components/katalyst/turbo/pagy_nav_component.rb +23 -0
  20. data/app/components/katalyst/turbo/table_component.rb +48 -0
  21. data/app/models/concerns/katalyst/tables/collection/core.rb +71 -0
  22. data/app/models/concerns/katalyst/tables/collection/pagination.rb +66 -0
  23. data/app/models/concerns/katalyst/tables/collection/sorting.rb +63 -0
  24. data/app/models/katalyst/tables/collection.rb +32 -0
  25. data/config/importmap.rb +7 -0
  26. data/lib/katalyst/tables/backend/sort_form.rb +16 -2
  27. data/lib/katalyst/tables/backend.rb +8 -8
  28. data/lib/katalyst/tables/engine.rb +24 -0
  29. data/lib/katalyst/tables/frontend/helper.rb +8 -9
  30. data/lib/katalyst/tables/frontend.rb +6 -36
  31. data/lib/katalyst/tables/version.rb +1 -1
  32. data/lib/katalyst/tables.rb +5 -0
  33. metadata +54 -9
  34. data/lib/katalyst/tables/frontend/builder/base.rb +0 -63
  35. data/lib/katalyst/tables/frontend/builder/body_cell.rb +0 -31
  36. data/lib/katalyst/tables/frontend/builder/body_row.rb +0 -29
  37. data/lib/katalyst/tables/frontend/builder/header_cell.rb +0 -55
  38. data/lib/katalyst/tables/frontend/builder/header_row.rb +0 -23
  39. 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: 5482118f21175e8c1dbff1a397fa7b5ff68480783a2c47da2a92c0436c66e739
4
- data.tar.gz: 99e70f74ef353cce895305e5ad39a98dc690efd7f0c68d4305867fbcdc23c941
3
+ metadata.gz: 2a572503d453b3b8b98a82942a48c5e2772cec4023417e8db71ed2cd8299331a
4
+ data.tar.gz: 4761c4fa4b21aa8c91f70c2118e27bd68dfd700ffc419ed6f7eae299b582710f
5
5
  SHA512:
6
- metadata.gz: 219380d0ccb99e001c9604594d0aa68a58925bbd745d87898af359316192e61727d3549c4ab64198c31656c663ec00809d93ddc8034921143f8169fcb49f7769
7
- data.tar.gz: b9d8a9e03604f9b39389d218bfceb4d354343bd0d1da4ff3bd7ca55f66446ba8f5fbc8430b9d5b22974f1210eed344ce0d579bccb209e29a0d35ca59982d4c63
6
+ metadata.gz: aa9c1dc28479dc744c18804999d5f34ea9937e1500dbe8c01a1f2c183439077dfdd7047dbb8ce2ea0f93ef43bcfcea15eee6d08b2606f3682a00747e590cbc3b
7
+ data.tar.gz: 45c36c312e7f6efa8b11409d4822d135c961eba6ade51055b4671371b3b1a6e25a01c9fd1e059c0a89fa5da6bea3e7fe56fda267a1b3d7bfdf51e515b7b8a696
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
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
+
18
+ ## [2.0.0]
19
+
20
+ - Replaces builders with view_components
21
+ We want view components to be the way to build custom tables, and add more
22
+ complete features to the gem. This will be a breaking change, but we have
23
+ tried hard to retain compatibility with existing code. Unless you are using
24
+ a custom builder then it's unlikely that you will see any changes.
25
+ - If you are using custom builders, then you will need to update them to use
26
+ view components. See [[README.md]] for examples.
27
+
3
28
  ## [1.1.0] - 2023-07-18
4
29
 
5
30
  - Replaces `param_key` with `i18n_key` for attribute lookup in locale file
data/README.md CHANGED
@@ -7,87 +7,108 @@ 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", git: "https://github.com/katalyst/katalyst-tables", branch: "main"
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
22
- entry point is optional, as it's only required if you want to support sorting by column headers.
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.
23
25
 
24
- ### Frontend
26
+ ## Frontend
25
27
 
26
- Add `include Katalyst::Tables::Frontend` to your `ApplicationHelper` or similar.
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).
27
34
 
28
35
  ```erb
29
- <%= table_with collection: @people do |row, person| %>
30
- <%= row.cell :name %>
31
- <%= row.cell :email %>
32
- <%= row.cell :actions do %>
33
- <%= link_to "Edit", person %>
34
- <% end %>
36
+ <%# locals: { row:, person: nil } %>
37
+ <% row.cell :name do |cell| %>
38
+ <%= link_to cell.value, [:edit, person] %>
35
39
  <% end %>
40
+ <% row.cell :email %>
36
41
  ```
37
42
 
38
- `table_builder` will call your block once per row and accumulate the cells you generate into rows:
43
+ The table component will call your partial once per row and accumulate the cells
44
+ you generate into rows, including a header row:
39
45
 
40
46
  ```html
41
47
 
42
48
  <table>
43
- <thead>
44
- <tr>
45
- <th>Name</th>
46
- <th>Email</th>
47
- <th>Actions</th>
48
- </tr>
49
- </thead>
50
- <tbody>
51
- <tr>
52
- <td>Alice</td>
53
- <td>alice@acme.org</td>
54
- <td><a href="/people/1/edit">Edit</a></td>
55
- </tr>
56
- <tr>
57
- <td>Bob</td>
58
- <td>bob@acme.org</td>
59
- <td><a href="/people/2/edit">Edit</a></td>
60
- </tr>
61
- </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>
62
65
  </table>
63
66
  ```
64
67
 
65
- ### Options
68
+ You can customize the partial and/or the name of the resource in a similar style
69
+ to view partials:
66
70
 
67
- You can customise the options passed to the table, rows, and cells.
71
+ ```erb
72
+ <%= render Katalyst::TableComponent.new(collection: @employees, as: :person, partial: "person") %>
73
+ ```
68
74
 
69
- Tables support options via the call to `table_with`, similar to `form_with`.
75
+ ### Inline tables
76
+
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.
70
81
 
71
82
  ```erb
72
- <%= table_with collection: @people, id: "people-table" do |row, person| %>
73
- ...
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 %>
74
88
  <% end %>
75
89
  ```
76
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
+
77
101
  Cells support the same approach:
78
102
 
79
103
  ```erb
80
104
  <%= row.cell :name, class: "name" %>
81
105
  ```
82
106
 
83
- Rows do not get called directly, so instead you can call `options` on the row builder to customize the row tag
107
+ Rows do not get called directly, so instead you can assign to `html_attributes` on the row builder to customize row tag
84
108
  generation.
85
109
 
86
110
  ```erb
87
- <%= table_with collection: @people, id: "people-table" do |row, person| %>
88
- <% row.options data: { id: person.id } if row.body? %>
89
- ...
90
- <% end %>
111
+ <% row.html_attributes = { id: person.id } if row.body? %>
91
112
  ```
92
113
 
93
114
  Note: because the row builder gets called to generate the header row, you may need to guard calls that access the
@@ -95,21 +116,21 @@ Note: because the row builder gets called to generate the header row, you may ne
95
116
 
96
117
  #### Headers
97
118
 
98
- `table_builder` will automatically generate a header row for you by calling your block with no object. During this
99
- iteration, `row.header?` is true, `row.body?` is false, and the object (`person`) is nil.
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.
100
121
 
101
122
  All cells generated in the table header iteration will automatically be header cells, but you can also make header cells
102
123
  in your body rows by passing `heading: true` when you generate the cell.
103
124
 
104
125
  ```erb
105
- <%= row.cell :id, heading: true %>
126
+ <% row.cell :id, heading: true %>
106
127
  ```
107
128
 
108
- The table header cells default to showing the titleized column name, but you can customize this in one of two ways:
129
+ The table header cells default to showing the capitalized column name, but you can customize this in one of two ways:
109
130
 
110
131
  * Set the value inline
111
132
  ```erb
112
- <%= row.cell :id, label: "ID" %>
133
+ <% row.cell :id, label: "ID" %>
113
134
  ```
114
135
  * Define a translation for the attribute
115
136
  ```yml
@@ -132,8 +153,8 @@ the table cell. This is often all you need to do, but if you do want to customis
132
153
  the value you can pass a block instead:
133
154
 
134
155
  ```erb
135
- <%= row.cell :status do %>
136
- <%= person.password.present? ? "Active" : "Invited" %>
156
+ <% row.cell :status do %>
157
+ <%= person.password.present? ? "Active" : "Invited" %>
137
158
  <% end %>
138
159
  ```
139
160
 
@@ -141,20 +162,61 @@ In the context of the block you have access the cell builder if you simply
141
162
  want to extend the default behaviour:
142
163
 
143
164
  ```erb
144
- <%= row.cell :status do |cell| %>
145
- <%= link_to cell.value, person %>
165
+ <% row.cell :status do |cell| %>
166
+ <%= link_to cell.value, person %>
146
167
  <% end %>
147
168
  ```
148
169
 
149
- You can also call `options` on the cell builder, similar to the row builder, but
150
- please note that this will replace any options passed to the cell as arguments.
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.
173
+
174
+ ## Collections
151
175
 
152
- ### Sort
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.
153
181
 
154
- The major reason why you should use this gem, apart the convenience of the
155
- builder, is for adding efficient and simple column sorting to your tables.
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
+ ```
156
188
 
157
- Start by including the backend in your controller(s):
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):
158
220
 
159
221
  ```ruby
160
222
  include Katalyst::Tables::Backend
@@ -185,55 +247,47 @@ links and show the current sort state:
185
247
  <%= table_with collection: @people, sort: @sort do |row, person| %>
186
248
  <%= row.cell :name %>
187
249
  <%= row.cell :email %>
188
- <%= row.cell :actions do %>
189
- <%= link_to "Edit", person %>
190
- <% end %>
191
250
  <% end %>
192
251
  ```
193
252
 
194
- That's it! Any column that corresponds to an ActiveRecord attribute will now be
195
- automatically sortable in the frontend.
253
+ ## Pagination
196
254
 
197
- You can also add sorting to non-attribute columns by defining a scope in your
198
- model:
255
+ This gem designed to work with [pagy](https://github.com/ddnexus/pagy/).
199
256
 
200
- ```
201
- scope :order_by_status, ->(direction) { ... }
202
- ```
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.
259
+
260
+ `Katalyst::Tables::PagyNavComponent` can be used to render the pagination links
261
+ for a collection.
203
262
 
204
- Finally, you can use sort with a collection that is already ordered, but please
205
- note that the backend will call `reorder` if the user provides a sort option. If
206
- you want to provide a tie-breaker default ordering, the best way to do so is after
207
- calling `table_sort`.
263
+ ```erb
264
+ <%= render Katalyst::Tables::PagyNavComponent.new(collection: @people) %>
265
+ ```
208
266
 
209
- You may also want to whitelist the `sort` param if you encounter strong param warnings.
267
+ ## Turbo streams
210
268
 
211
- ### Pagination
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
- This gem designed to work with [pagy](https://github.com/ddnexus/pagy/).
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
 
215
278
  ```ruby
216
-
217
279
  def index
218
- @people = People.all
219
-
220
- @sort, @people = table_sort(@people) # sort
221
- @pagy, @people = pagy(@people) # then paginate
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
222
287
  end
223
288
  ```
224
289
 
225
- ```erb
226
- <%= table_with collection: @people, sort: @sort do |row, person| %>
227
- <%= row.cell :name %>
228
- <%= row.cell :email %>
229
- <%= row.cell :actions do %>
230
- <%= link_to "Edit", person %>
231
- <% end %>
232
- <% end %>
233
- <%== pagy_nav(@pagy) %>
234
- ```
235
-
236
- ### Customization
290
+ ## Customization
237
291
 
238
292
  A common pattern we use is to have a cell at the end of the table for actions. For example:
239
293
 
@@ -257,75 +311,53 @@ A common pattern we use is to have a cell at the end of the table for actions. F
257
311
  </table>
258
312
  ```
259
313
 
260
- You can write a custom builder that helps generate this type of table by adding the required classes and adding helpers
261
- for generating the actions. This allows for a declarative table syntax, something like this:
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:
262
317
 
263
318
  ```erb
264
- <%= table_with(collection: collection, builder: Test::ActionTable) do |row| %>
265
- <%= row.cell :name %>
266
- <%= row.actions do |cell| %>
319
+ <%= render ActionTableComponent.new(collection:) do |row| %>
320
+ <% row.cell :name %>
321
+ <% row.actions do |cell| %>
267
322
  <%= cell.action "Edit", :edit %>
268
323
  <%= cell.action "Delete", :delete, method: :delete %>
269
324
  <% end %>
270
325
  <% end %>
271
326
  ```
272
327
 
273
- And the custom builder:
328
+ And the customized component:
274
329
 
275
330
  ```ruby
276
- class ActionTable < Katalyst::Tables::Frontend::TableBuilder
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
285
-
286
- def table_header_cell(method, builder = ActionHeaderCell, **options)
287
- super
288
- end
289
-
290
- def table_body_row(object, builder = ActionBodyRow, &block)
291
- super
292
- end
331
+ class ActionTableComponent < Katalyst::TableComponent
293
332
 
294
- def table_body_cell(object, method, builder = ActionBodyCell, **options, &block)
295
- super
333
+ config.header_row = "ActionHeaderRow"
334
+ config.body_row = "ActionBodyRow"
335
+ config.body_cell = "ActionBodyCell"
336
+
337
+ def default_attributes
338
+ { class: "action-table" }
296
339
  end
297
340
 
298
- class ActionHeaderRow < Katalyst::Tables::Frontend::Builder::HeaderRow
341
+ class ActionHeaderRow < Katalyst::Tables::HeaderRowComponent
299
342
  def actions(&block)
300
- cell(:actions, class: "actions", label: "")
343
+ cell(:actions, class: "actions", label: "", &block)
301
344
  end
302
345
  end
303
346
 
304
- class ActionHeaderCell < Katalyst::Tables::Frontend::Builder::HeaderCell
305
- end
306
-
307
- class ActionBodyRow < Katalyst::Tables::Frontend::Builder::BodyRow
347
+ class ActionBodyRow < Katalyst::Tables::BodyRowComponent
308
348
  def actions(&block)
309
349
  cell(:actions, class: "actions", &block)
310
350
  end
311
351
  end
312
352
 
313
- class ActionBodyCell < Katalyst::Tables::Frontend::Builder::BodyCell
314
- def action(label, href, **opts)
315
- content_tag :a, label, { href: href }.merge(opts)
353
+ class ActionBodyCell < Katalyst::Tables::BodyCellComponent
354
+ def action(label, href, **attrs)
355
+ content_tag(:a, label, href: href, **attrs)
316
356
  end
317
357
  end
318
358
  end
319
359
  ```
320
360
 
321
- If you have a table builder you want to reuse, you can set it as a default for some or all of your controllers:
322
-
323
- ```html
324
- class ApplicationController < ActiveController::Base
325
- default_table_builder ActionTableBuilder
326
- end
327
- ```
328
-
329
361
  ## Development
330
362
 
331
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