katalyst-tables 3.0.0.beta1 → 3.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -2
  3. data/README.md +56 -190
  4. data/app/assets/builds/katalyst/tables.esm.js +17 -47
  5. data/app/assets/builds/katalyst/tables.js +17 -47
  6. data/app/assets/builds/katalyst/tables.min.js +1 -1
  7. data/app/assets/builds/katalyst/tables.min.js.map +1 -1
  8. data/app/components/concerns/katalyst/tables/has_table_content.rb +17 -8
  9. data/app/components/concerns/katalyst/tables/identifiable.rb +51 -0
  10. data/app/components/concerns/katalyst/tables/orderable.rb +35 -105
  11. data/app/components/concerns/katalyst/tables/selectable.rb +18 -75
  12. data/app/components/concerns/katalyst/tables/sortable.rb +51 -17
  13. data/app/components/katalyst/table_component.html.erb +4 -4
  14. data/app/components/katalyst/table_component.rb +271 -53
  15. data/app/components/katalyst/tables/body_row_component.html.erb +5 -0
  16. data/app/components/katalyst/tables/body_row_component.rb +4 -31
  17. data/app/components/katalyst/tables/cell_component.rb +85 -0
  18. data/app/components/katalyst/tables/{body → cells}/boolean_component.rb +8 -2
  19. data/app/components/katalyst/tables/{body → cells}/currency_component.rb +7 -7
  20. data/app/components/katalyst/tables/{body → cells}/date_component.rb +12 -9
  21. data/app/components/katalyst/tables/{body → cells}/date_time_component.rb +13 -10
  22. data/app/components/katalyst/tables/{body → cells}/number_component.rb +5 -5
  23. data/app/components/katalyst/tables/cells/ordinal_component.rb +44 -0
  24. data/app/components/katalyst/tables/{body → cells}/rich_text_component.rb +8 -5
  25. data/app/components/katalyst/tables/cells/select_component.rb +39 -0
  26. data/app/components/katalyst/tables/data.rb +30 -0
  27. data/app/components/katalyst/tables/header_row_component.html.erb +5 -0
  28. data/app/components/katalyst/tables/header_row_component.rb +4 -25
  29. data/app/components/katalyst/tables/label.rb +37 -0
  30. data/app/components/katalyst/tables/orderable/form_component.rb +38 -0
  31. data/app/components/katalyst/tables/selectable/form_component.html.erb +3 -3
  32. data/app/components/katalyst/tables/selectable/form_component.rb +8 -11
  33. data/app/controllers/concerns/katalyst/tables/backend.rb +2 -28
  34. data/app/helpers/katalyst/tables/frontend.rb +48 -2
  35. data/app/javascript/tables/application.js +0 -5
  36. data/app/javascript/tables/orderable/form_controller.js +8 -6
  37. data/app/javascript/tables/orderable/item_controller.js +9 -0
  38. data/app/models/concerns/katalyst/tables/collection/core.rb +6 -1
  39. data/app/models/concerns/katalyst/tables/collection/sorting.rb +85 -17
  40. data/app/models/katalyst/tables/collection/array.rb +38 -0
  41. data/app/models/katalyst/tables/collection/base.rb +4 -0
  42. data/config/locales/tables.en.yml +0 -6
  43. data/lib/katalyst/tables/config.rb +23 -0
  44. data/lib/katalyst/tables.rb +9 -0
  45. metadata +22 -29
  46. data/app/components/concerns/katalyst/tables/body/typed_columns.rb +0 -132
  47. data/app/components/concerns/katalyst/tables/configurable_component.rb +0 -52
  48. data/app/components/concerns/katalyst/tables/header/typed_columns.rb +0 -179
  49. data/app/components/katalyst/tables/body/attachment_component.rb +0 -58
  50. data/app/components/katalyst/tables/body/link_component.rb +0 -40
  51. data/app/components/katalyst/tables/body_cell_component.rb +0 -55
  52. data/app/components/katalyst/tables/header/attachment_component.rb +0 -15
  53. data/app/components/katalyst/tables/header/boolean_component.rb +0 -15
  54. data/app/components/katalyst/tables/header/currency_component.rb +0 -15
  55. data/app/components/katalyst/tables/header/date_component.rb +0 -15
  56. data/app/components/katalyst/tables/header/date_time_component.rb +0 -15
  57. data/app/components/katalyst/tables/header/link_component.rb +0 -15
  58. data/app/components/katalyst/tables/header/number_component.rb +0 -15
  59. data/app/components/katalyst/tables/header/rich_text_component.rb +0 -15
  60. data/app/components/katalyst/tables/header_cell_component.rb +0 -97
  61. data/app/helpers/katalyst/tables/frontend/helper.rb +0 -31
  62. data/app/javascript/tables/turbo/collection_controller.js +0 -38
  63. data/app/models/katalyst/tables/collection/sort_form.rb +0 -120
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac8dcee0526a034b23487868fb642c389e69c323aaf463a7aa6896bf777d5bc1
4
- data.tar.gz: 8cea19cc8467137e533b9146daa65f51dacdf78763e4fde7904542261cbc66b3
3
+ metadata.gz: cda2701479ecd72f9964ab539c5203bd0c300aeb962cf926552904575474ddea
4
+ data.tar.gz: 6f43fc5e31c8536811e28cba0db4be3c0967db24d06ad22f2a72c89512030d87
5
5
  SHA512:
6
- metadata.gz: ea6efd6150d3bb2d9f9d48c89920831f97e6dede515cfcd99ec049f81328c32c373fbf49cfd798adde0f1d8055819e1eb4f40931f8ac392809006c384c219c98
7
- data.tar.gz: 66480c79be3aa1762145c3d92723f284ed61749d46029600d20541820e2d831e266fa79ffa0de9ad77efc750ba80f274f7fee86267b7a946cad78ba8c49773da
6
+ metadata.gz: 14acdb5b09216c84c06b2082ed2a11888f0930b1807f7ade14cbc7fa02751f3c888a69b5397df7eb028395b7902eaad47abaa710e33fa24ecc6d6ed98757616f
7
+ data.tar.gz: f3721287c65a5aa0d4e81c36d5f704b90e2995cf372376ba19c9b03cb5b0129a5f4c8a56b369a09b2c62e72eb585163d656abb303a8dd62aa8b7912193f710b5
data/CHANGELOG.md CHANGED
@@ -1,8 +1,18 @@
1
- ## [3.0.0.beta1]
2
- - Breaking change: remove Turbo Streams from table and pagination components, focus preservation is handled via Turbo Morph
1
+ ## [3.0.0]
2
+
3
+ - Breaking change: remove Turbo Streams from table and pagination components,
4
+ focus preservation is handled via Turbo Morph
3
5
  - Improve spec coverage
6
+ - Re-write internals to make it easier to extend and customize
4
7
  - Update examples in [README](README.md) and [docs](/docs) to reflect changes
5
8
 
9
+ If you're upgrading from 2.x, you'll need to change your controllers to use
10
+ the recommendations from the README. The changes should be straightforward,
11
+ but you will need to enable morphing to allow focus preservation.
12
+
13
+ In general, we don't recommend using row partials anymore, as it's easier to
14
+ read the code when the row is defined in the index view.
15
+
6
16
  ## [2.6.0]
7
17
 
8
18
  - Added table row selection
data/README.md CHANGED
@@ -24,33 +24,48 @@ import tables from "@katalyst/tables";
24
24
  application.load(tables);
25
25
  ```
26
26
 
27
+ ```scss
28
+ // app/assets/stylesheets/application.scss
29
+ @use "@katalyst/tables";
30
+ ```
31
+
27
32
  ## Usage
28
33
 
29
34
  This gem provides entry points for backend and frontend concerns:
30
- * `Katalyst::TableComponent` can be used render encapsulated tables, it calls a
31
- partial for each row.
35
+ * `Katalyst::TableComponent` can be used render encapsulated tables.
32
36
  * `Katalyst::Tables::Frontend` provides `table_with` for inline table generation
33
37
  * `Katalyst::Tables::Collection::Base` provides a default entry point for
34
38
  building collections in your controller actions.
35
39
 
36
- ## Frontend
40
+ ### Frontend
41
+
42
+ Add `include Katalyst::Tables::Frontend` to your `ApplicationHelper` or similar.
43
+
44
+ You can use the `table_with` helper to generate a table inline in your view without explicitly interacting with the
45
+ table component. This is the preferred approach when creating tables. However, if the table is complex or you need to
46
+ reuse it, you should consider moving the definition of the row into a partial.
37
47
 
38
- Use `Katalyst::TableComponent` to build a table component from an ActiveRecord
39
- collection, or from a `Katalyst::Tables::Collection::Base` instance.
48
+ ```erb
49
+ <%= table_with collection: @people do |row, person| %>
50
+ <% row.text :name do |cell| %>
51
+ <%= link_to cell.value, [:edit, person] %>
52
+ <% end %>
53
+ <% row.text :email %>
54
+ <% end %>
55
+ ```
40
56
 
41
- For example, if you render `Katalyst::TableComponent.new(collection: @people)`,
42
- the table component will look for a partial called `_person.html+row.erb` and
43
- render it for each row (and once for the header row).
57
+ By not providing a block to the `table_with` call, the gem will look for a partial called `_person.html+row.erb` to
58
+ render each row:
44
59
 
45
60
  ```erb
46
61
  <%# locals: { row:, person: nil } %>
47
- <% row.cell :name do |cell| %>
62
+ <% row.text :name do |cell| %>
48
63
  <%= link_to cell.value, [:edit, person] %>
49
64
  <% end %>
50
- <% row.cell :email %>
65
+ <% row.text :email %>
51
66
  ```
52
67
 
53
- The table component will call your partial once per row and accumulate the cells
68
+ The table will call your block / partial once per row and accumulate the cells
54
69
  you generate into rows, including a header row:
55
70
 
56
71
  ```html
@@ -79,52 +94,36 @@ You can customize the partial and/or the name of the resource in a similar style
79
94
  to view partials:
80
95
 
81
96
  ```erb
82
- <%= render Katalyst::TableComponent.new(collection: @employees, as: :person, partial: "person") %>
83
- ```
84
-
85
- ### Inline tables
86
-
87
- You can use the `table_with` helper to generate a table inline in your view without explicitly interacting with the
88
- table component. This is primarily intended for backwards compatibility, but it can be useful for simple tables.
89
-
90
- Add `include Katalyst::Tables::Frontend` to your `ApplicationHelper` or similar.
91
-
92
- ```erb
93
- <%= table_with collection: @people do |row, person| %>
94
- <% row.cell :name do |cell| %>
95
- <%= link_to cell.value, [:edit, person] %>
96
- <% end %>
97
- <% row.cell :email %>
98
- <% end %>
97
+ <%= table_with(collection: @employees, as: :person, partial: "person") %>
99
98
  ```
100
99
 
101
100
  ### HTML Attributes
102
101
 
103
102
  You can add custom attributes on table, row, and cell tags.
104
103
 
105
- The table tag takes attributes passed to `TableComponent` or via the call to `table_with`, similar to `form_with`:
104
+ The table tag takes attributes passed to `table_with` helper, similar to `form_with`:
106
105
 
107
106
  ```erb
108
- <%= TableComponent.new(collection: @people, id: "people-table")
107
+ <%= table_with(collection: @people, id: "people-table")
109
108
  ```
110
109
 
111
110
  Cells support the same approach:
112
111
 
113
112
  ```erb
114
- <%= row.cell :name, class: "name" %>
113
+ <%= row.text :name, class: "name" %>
115
114
  ```
116
115
 
117
116
  Rows do not get called directly, so instead you can assign to `html_attributes` on the row builder to customize row tag
118
117
  generation.
119
118
 
120
119
  ```erb
121
- <% row.html_attributes = { id: person.id } if row.body? %>
120
+ <% row.update_html_attributes(id: person.id) if row.body? %>
122
121
  ```
123
122
 
124
123
  Note: because the row builder gets called to generate the header row, you may need to guard calls that access the
125
124
  `person` directly as shown in the previous example. You could also check whether `person` is present.
126
125
 
127
- #### Headers
126
+ ### Headers
128
127
 
129
128
  Tables will automatically generate a header row for you by calling your row partial or provided block with no object.
130
129
  During this call, `row.header?` is true, `row.body?` is false, and the object (`person`) is nil.
@@ -133,14 +132,14 @@ All cells generated in the table header iteration will automatically be header c
133
132
  in your body rows by passing `heading: true` when you generate the cell.
134
133
 
135
134
  ```erb
136
- <% row.cell :id, heading: true %>
135
+ <% row.number :id, heading: true %>
137
136
  ```
138
137
 
139
138
  The table header cells default to showing the capitalized column name, but you can customize this in one of two ways:
140
139
 
141
140
  * Set the value inline
142
141
  ```erb
143
- <% row.cell :id, label: "ID" %>
142
+ <% row.number :id, label: "ID" %>
144
143
  ```
145
144
  * Define a translation for the attribute
146
145
  ```yml
@@ -163,194 +162,61 @@ the table cell. This is often all you need to do, but if you do want to customis
163
162
  the value you can pass a block instead:
164
163
 
165
164
  ```erb
166
- <% row.cell :status do %>
165
+ <% row.text :status do %>
167
166
  <%= person.password.present? ? "Active" : "Invited" %>
168
167
  <% end %>
169
168
  ```
170
169
 
171
- In the context of the block you have access the cell builder if you simply
170
+ In the context of the block you have access the cell component if you simply
172
171
  want to extend the default behaviour:
173
172
 
174
173
  ```erb
175
- <% row.cell :status do |cell| %>
176
- <%= link_to cell.value, person %>
174
+ <%# @type [Katalyst::Tables::CellComponent] cell %>
175
+ <% row.text :name do |cell| %>
176
+ <%= link_to cell, person %>
177
177
  <% end %>
178
178
  ```
179
179
 
180
- You can also assign to `html_attributes` on the cell builder, similar to the row
181
- builder, but please note that this will replace any options passed to the cell
182
- as arguments.
180
+ You can also update `html_attributes` on the cell builder, similar to the row
181
+ builder, see `katalyst-html-attributes` for details.
183
182
 
184
183
  ## Collections
185
184
 
186
185
  The `Katalyst::Tables::Collection::Base` class provides a convenient way to
187
186
  manage collections in your controller actions. It is designed to be used with
188
187
  Pagy for pagination and provides built-in sorting when used with ActiveRecord
189
- collections. Sorting and Pagination are off by default, but you can create
190
- a custom `ApplicationCollection` class that sets them on by default.
188
+ collections. Sorting and Pagination are off by default, you can either set them
189
+ on creation or create a custom `Collection` class that sets them on by default:
191
190
 
192
191
  ```ruby
193
- class ApplicationCollection < Katalyst::Tables::Collection::Base
194
- config.sorting = "name" # requires models have a name attribute
192
+ # in #index
193
+ Katalyst::Tables::Collection::Base.new(sorting: "name asc", pagination: true)
194
+ # or as a nested class in your controller
195
+ class Collection < Katalyst::Tables::Collection::Base
196
+ config.sorting = "name asc" # requires models have a name attribute
195
197
  config.pagination = true
196
198
  end
197
199
  ```
198
200
 
199
- You can then use this class in your controller actions:
200
-
201
- ```ruby
202
- class PeopleController < ApplicationController
203
- def index
204
- @people = ApplicationCollection.new.with_params(params).apply(People.all)
205
- end
206
- end
207
- ```
208
-
209
- Collections can be passed directly to `TableComponent` and it will automatically
201
+ Collections can be passed directly to `table_with` method and it will automatically
210
202
  detect features such as sorting and generate the appropriate table header links.
211
203
 
212
204
  ```erb
213
- <%= render TableComponent.new(collection: @people) %>
214
- ```
215
-
216
- ## Sort
217
-
218
- When sort is enabled, table columns will be automatically sortable in the
219
- frontend for any column that corresponds to an attribute on the model. You can
220
- also add sorting to non-attribute columns by defining a scope in your
221
- model:
222
-
223
- ```
224
- scope :order_by_status, ->(direction) { ... }
225
- ```
226
-
227
- You can also use sort without using collections, this was the primary backend
228
- interface for V1 and takes design cues from Pagy. Start by including the backend
229
- in your controller(s):
230
-
231
- ```ruby
232
- include Katalyst::Tables::Backend
233
- ```
234
-
235
- Now, in your controller index actions, you can sort your active record
236
- collections based on the `sort` param which is appended to the current URL as a
237
- get parameter when a user clicks on a column header.
238
-
239
- Building on our example from earlier:
240
-
241
- ```ruby
242
- class PeopleController < ApplicationController
243
- include Katalyst::Tables::Backend
244
-
245
- def index
246
- @people = People.all
247
-
248
- @sort, @people = table_sort(@people) # sort
249
- end
250
- end
251
- ```
252
-
253
- You then add the sort form object to your view so that it can add column header
254
- links and show the current sort state:
255
-
256
- ```erb
257
- <%= table_with collection: @people, sort: @sort do |row, person| %>
258
- <%= row.cell :name %>
259
- <%= row.cell :email %>
260
- <% end %>
261
- ```
262
-
263
- ## Pagination
264
-
265
- This gem designed to work with [pagy](https://github.com/ddnexus/pagy/).
266
-
267
- If you use collections and enable pagination then pagy will be called internally
268
- and the pagy metadata will be available as `pagination` on the collection.
269
-
270
- `Katalyst::Tables::PagyNavComponent` can be used to render the pagination links
271
- for a collection.
272
-
273
- ```erb
274
- <%= render Katalyst::Tables::PagyNavComponent.new(collection: @people) %>
205
+ <%= table_with(collection:) %>
275
206
  ```
276
207
 
277
208
  ## Extensions
278
209
 
279
- The following extensions are available:
210
+ The following extensions are available and activated by default:
280
211
 
212
+ * [Identifiable](docs/identifiable.md) - adds default dom ids to the table and data rows.
281
213
  * [Orderable](docs/orderable.md) - adds bulk-update for 'ordinal' columns via dragging rows in the table.
214
+ * [Pagination](docs/pagination.md) - handles paginating of data in the collection.
282
215
  * [Selectable](docs/selectable.md) - adds bulk-action support for rows in the table.
216
+ * [Sortable](docs/sortable.md) - table column headers that can be sorted will be wrapped in links.
217
+ * [Customization](docs/customization.md) - customize the table and cell rendering.
283
218
 
284
- ## Customization
285
-
286
- A common pattern we use is to have a cell at the end of the table for actions. For example:
287
-
288
- ```html
289
- <table class="action-table">
290
- <thead>
291
- <tr>
292
- <th>Name</th>
293
- <th class="actions"></th>
294
- </tr>
295
- </thead>
296
- <tbody>
297
- <tr>
298
- <td>Alice</td>
299
- <td class="actions">
300
- <a href="/people/1/edit">Edit</a>
301
- <a href="/people/1" method="delete">Delete</a>
302
- </td>
303
- </tr>
304
- </tbody>
305
- </table>
306
- ```
307
-
308
- You can write a custom component that helps generate this type of table by
309
- adding the required classes and adding helpers for generating the actions.
310
- This allows for a declarative table syntax, something like this:
311
-
312
- ```erb
313
- <%= render ActionTableComponent.new(collection:) do |row| %>
314
- <% row.cell :name %>
315
- <% row.actions do |cell| %>
316
- <%= cell.action "Edit", :edit %>
317
- <%= cell.action "Delete", :delete, method: :delete %>
318
- <% end %>
319
- <% end %>
320
- ```
321
-
322
- And the customized component:
323
-
324
- ```ruby
325
- class ActionTableComponent < Katalyst::TableComponent
326
-
327
- config.header_row = "ActionHeaderRow"
328
- config.body_row = "ActionBodyRow"
329
- config.body_cell = "ActionBodyCell"
330
-
331
- def default_html_attributes
332
- { class: "action-table" }
333
- end
334
-
335
- class ActionHeaderRow < Katalyst::Tables::HeaderRowComponent
336
- def actions(&block)
337
- cell(:actions, class: "actions", label: "", &block)
338
- end
339
- end
340
-
341
- class ActionBodyRow < Katalyst::Tables::BodyRowComponent
342
- def actions(&block)
343
- cell(:actions, class: "actions", &block)
344
- end
345
- end
346
-
347
- class ActionBodyCell < Katalyst::Tables::BodyCellComponent
348
- def action(label, href, **attrs)
349
- content_tag(:a, label, href: href, **attrs)
350
- end
351
- end
352
- end
353
- ```
219
+ You can disable extensions by altering the `Katalyst::Tables.config.component_extensions` before initialization.
354
220
 
355
221
  ## Development
356
222
 
@@ -1,41 +1,4 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
- import { Turbo } from '@hotwired/turbo-rails';
3
-
4
- class TurboCollectionController extends Controller {
5
- static values = {
6
- query: String,
7
- sort: String,
8
- };
9
-
10
- queryValueChanged(query) {
11
- Turbo.navigator.history.replace(this.#url(query));
12
- }
13
-
14
- sortValueChanged(sort) {
15
- document.querySelectorAll(this.#sortSelector).forEach((input) => {
16
- if (input) input.value = sort;
17
- });
18
- }
19
-
20
- get #sortSelector() {
21
- return "input[name='sort']";
22
- }
23
-
24
- #url(query) {
25
- const frame = this.element.closest("turbo-frame");
26
- let url;
27
-
28
- if (frame) {
29
- url = new URL(frame.baseURI);
30
- } else {
31
- url = new URL(window.location.href);
32
- }
33
-
34
- url.search = query;
35
-
36
- return url;
37
- }
38
- }
39
2
 
40
3
  class OrderableRowController extends Controller {
41
4
  static values = {
@@ -85,6 +48,15 @@ class OrderableRowController extends Controller {
85
48
  this.index = index;
86
49
  }
87
50
 
51
+ /** Retrieve params for use in the form */
52
+ params(scope) {
53
+ const { id_name, id_value, index_name } = this.paramsValue;
54
+ return [
55
+ { name: `${scope}[${id_value}][${id_name}]`, value: this.id },
56
+ { name: `${scope}[${id_value}][${index_name}]`, value: this.index },
57
+ ];
58
+ }
59
+
88
60
  /**
89
61
  * Restore any visual changes made during drag and remove the drag state.
90
62
  */
@@ -450,13 +422,15 @@ class DragState {
450
422
  }
451
423
 
452
424
  class OrderableFormController extends Controller {
425
+ static values = { scope: String };
426
+
453
427
  add(item) {
454
- const { id_name, id_value, index_name } = item.paramsValue;
455
- this.element.insertAdjacentHTML(
456
- "beforeend",
457
- `<input type="hidden" name="${id_name}" value="${id_value}" data-generated>
458
- <input type="hidden" name="${index_name}" value="${item.index}" data-generated>`,
459
- );
428
+ item.params(this.scopeValue).forEach(({ name, value }) => {
429
+ this.element.insertAdjacentHTML(
430
+ "beforeend",
431
+ `<input type="hidden" name="${name}" value="${value}" data-generated>`,
432
+ );
433
+ });
460
434
  }
461
435
 
462
436
  submit() {
@@ -560,10 +534,6 @@ class SelectionItemController extends Controller {
560
534
  }
561
535
 
562
536
  const Definitions = [
563
- {
564
- identifier: "tables--turbo--collection",
565
- controllerConstructor: TurboCollectionController,
566
- },
567
537
  {
568
538
  identifier: "tables--orderable--item",
569
539
  controllerConstructor: OrderableRowController,
@@ -1,41 +1,4 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
- import { Turbo } from '@hotwired/turbo-rails';
3
-
4
- class TurboCollectionController extends Controller {
5
- static values = {
6
- query: String,
7
- sort: String,
8
- };
9
-
10
- queryValueChanged(query) {
11
- Turbo.navigator.history.replace(this.#url(query));
12
- }
13
-
14
- sortValueChanged(sort) {
15
- document.querySelectorAll(this.#sortSelector).forEach((input) => {
16
- if (input) input.value = sort;
17
- });
18
- }
19
-
20
- get #sortSelector() {
21
- return "input[name='sort']";
22
- }
23
-
24
- #url(query) {
25
- const frame = this.element.closest("turbo-frame");
26
- let url;
27
-
28
- if (frame) {
29
- url = new URL(frame.baseURI);
30
- } else {
31
- url = new URL(window.location.href);
32
- }
33
-
34
- url.search = query;
35
-
36
- return url;
37
- }
38
- }
39
2
 
40
3
  class OrderableRowController extends Controller {
41
4
  static values = {
@@ -85,6 +48,15 @@ class OrderableRowController extends Controller {
85
48
  this.index = index;
86
49
  }
87
50
 
51
+ /** Retrieve params for use in the form */
52
+ params(scope) {
53
+ const { id_name, id_value, index_name } = this.paramsValue;
54
+ return [
55
+ { name: `${scope}[${id_value}][${id_name}]`, value: this.id },
56
+ { name: `${scope}[${id_value}][${index_name}]`, value: this.index },
57
+ ];
58
+ }
59
+
88
60
  /**
89
61
  * Restore any visual changes made during drag and remove the drag state.
90
62
  */
@@ -450,13 +422,15 @@ class DragState {
450
422
  }
451
423
 
452
424
  class OrderableFormController extends Controller {
425
+ static values = { scope: String };
426
+
453
427
  add(item) {
454
- const { id_name, id_value, index_name } = item.paramsValue;
455
- this.element.insertAdjacentHTML(
456
- "beforeend",
457
- `<input type="hidden" name="${id_name}" value="${id_value}" data-generated>
458
- <input type="hidden" name="${index_name}" value="${item.index}" data-generated>`,
459
- );
428
+ item.params(this.scopeValue).forEach(({ name, value }) => {
429
+ this.element.insertAdjacentHTML(
430
+ "beforeend",
431
+ `<input type="hidden" name="${name}" value="${value}" data-generated>`,
432
+ );
433
+ });
460
434
  }
461
435
 
462
436
  submit() {
@@ -560,10 +534,6 @@ class SelectionItemController extends Controller {
560
534
  }
561
535
 
562
536
  const Definitions = [
563
- {
564
- identifier: "tables--turbo--collection",
565
- controllerConstructor: TurboCollectionController,
566
- },
567
537
  {
568
538
  identifier: "tables--orderable--item",
569
539
  controllerConstructor: OrderableRowController,
@@ -1,2 +1,2 @@
1
- import{Controller as t}from"@hotwired/stimulus";import{Turbo as e}from"@hotwired/turbo-rails";class s{constructor(t,e,s){this.cursorOffset=e.offsetY,this.initialPosition=e.target.offsetTop-t.offsetTop,this.targetId=s}updateCursor(t,e,s,i){this.listOffset=t.getBoundingClientRect().top;let r=s.clientY-this.listOffset-this.cursorOffset;this.#t(t,e,r,i)}updateScroll(t,e,s){const i=this.listOffset;this.listOffset=t.getBoundingClientRect().top;const r=i-this.listOffset,a=this.position+r;this.#t(t,e,a,s)}#t(t,e,s,i){s=Math.max(s,0),s=Math.min(s,t.offsetHeight-e.offsetHeight),this.position=s;i(s-this.initialPosition)}}class i extends t{static outlets=["tables--selection--form"];static values={params:Object,checked:Boolean};tablesSelectionFormOutletConnected(t){this.checkedValue=t.isSelected(this.id)}change(t){t.preventDefault(),this.checkedValue=this.tablesSelectionFormOutlet.toggle(this.id)}get id(){return this.paramsValue.id}checkedValueChanged(t){this.element.querySelector("input").checked=t}}const r=[{identifier:"tables--turbo--collection",controllerConstructor:class extends t{static values={query:String,sort:String};queryValueChanged(t){e.navigator.history.replace(this.#e(t))}sortValueChanged(t){document.querySelectorAll(this.#s).forEach((e=>{e&&(e.value=t)}))}get#s(){return"input[name='sort']"}#e(t){const e=this.element.closest("turbo-frame");let s;return s=e?new URL(e.baseURI):new URL(window.location.href),s.search=t,s}}},{identifier:"tables--orderable--item",controllerConstructor:class extends t{static values={params:Object};connect(){var t;this.index=(t=this.row,Array.from(t.parentElement.children).indexOf(t))}paramsValueChanged(t){this.id=t.id_value}dragUpdate(t){this.dragOffset=t,this.row.style.position="relative",this.row.style.top=t+"px",this.row.style.zIndex="1",this.row.toggleAttribute("dragging",!0)}updateVisually(t){this.row.style.position="relative",this.row.style.top=this.row.offsetHeight*(t-this.dragIndex)+"px"}updateIndex(t){this.index=t}reset(){delete this.dragOffset,this.row.removeAttribute("style"),this.row.removeAttribute("dragging")}get hasChanges(){return this.paramsValue.index_value!==this.index}get dragIndex(){return this.dragOffset&&0!==this.dragOffset?this.index+Math.round(this.dragOffset/this.row.offsetHeight):this.index}get comparisonIndex(){return this.dragOffset?this.dragIndex+(this.dragOffset>0?.5:-.5):this.index}get row(){return this.element.parentElement}}},{identifier:"tables--orderable--list",controllerConstructor:class extends t{static outlets=["tables--orderable--item","tables--orderable--form"];startDragging(t){this.dragState=t,document.addEventListener("mousemove",this.mousemove),document.addEventListener("mouseup",this.mouseup),window.addEventListener("scroll",this.scroll,!0),this.element.style.position="relative"}stopDragging(){const t=this.dragState;return delete this.dragState,document.removeEventListener("mousemove",this.mousemove),document.removeEventListener("mouseup",this.mouseup),window.removeEventListener("scroll",this.scroll,!0),this.element.removeAttribute("style"),this.tablesOrderableItemOutlets.forEach((t=>t.reset())),t}drop(){const t=this.dragItem;if(!t)return;const e=t.dragIndex,s=this.tablesOrderableItemOutlets[e];s&&(e<t.index?s.row.insertAdjacentElement("beforebegin",t.row):e>t.index&&s.row.insertAdjacentElement("afterend",t.row),this.tablesOrderableItemOutlets.forEach(((t,e)=>t.updateIndex(e))),this.commitChanges())}commitChanges(){this.tablesOrderableFormOutlet.clear(),this.tablesOrderableItemOutlets.forEach((t=>{t.hasChanges&&this.tablesOrderableFormOutlet.add(t)})),this.tablesOrderableFormOutlet.submit()}mousedown(t){if(this.isDragging)return;const e=this.#i(t.target);e&&(t.preventDefault(),this.startDragging(new s(this.element,t,e.id)),this.dragState.updateCursor(this.element,e.row,t,this.animate))}mousemove=t=>{this.isDragging&&(t.preventDefault(),this.ticking||(this.ticking=!0,window.requestAnimationFrame((()=>{this.ticking=!1,this.dragState.updateCursor(this.element,this.dragItem.row,t,this.animate)}))))};scroll=t=>{this.isDragging&&!this.ticking&&(this.ticking=!0,window.requestAnimationFrame((()=>{this.ticking=!1,this.dragState.updateScroll(this.element,this.dragItem.row,this.animate)})))};mouseup=t=>{this.isDragging&&(this.drop(),this.stopDragging(),this.tablesOrderableFormOutlets.forEach((t=>delete t.dragState)))};tablesOrderableFormOutletConnected(t,e){t.dragState&&this.startDragging(t.dragState)}tablesOrderableFormOutletDisconnected(t,e){this.isDragging&&(t.dragState=this.stopDragging())}animate=t=>{const e=this.dragItem;e.dragUpdate(t),this.#r.forEach(((t,s)=>{t!==e&&t.updateVisually(s)}))};get isDragging(){return!!this.dragState}get dragItem(){return this.isDragging?this.tablesOrderableItemOutlets.find((t=>t.id===this.dragState.targetId)):null}get#r(){return this.tablesOrderableItemOutlets.toSorted(((t,e)=>t.comparisonIndex-e.comparisonIndex))}#i(t){return this.tablesOrderableItemOutlets.find((e=>e.element===t))}}},{identifier:"tables--orderable--form",controllerConstructor:class extends t{add(t){const{id_name:e,id_value:s,index_name:i}=t.paramsValue;this.element.insertAdjacentHTML("beforeend",`<input type="hidden" name="${e}" value="${s}" data-generated>\n <input type="hidden" name="${i}" value="${t.index}" data-generated>`)}submit(){0!==this.inputs.length&&this.element.requestSubmit()}clear(){this.inputs.forEach((t=>t.remove()))}get inputs(){return this.element.querySelectorAll("input[data-generated]")}}},{identifier:"tables--selection--form",controllerConstructor:class extends t{static values={count:Number,primaryKey:{type:String,default:"id"}};static targets=["count","singular","plural"];connect(){this.countValue=this.inputs.length}toggle(t){const e=this.input(t);return e?e.remove():this.element.insertAdjacentHTML("beforeend",`<input type="hidden" name="${this.primaryKeyValue}[]" value="${t}">`),this.countValue=this.inputs.length,!e}isSelected(t){return!!this.input(t)}get inputs(){return this.element.querySelectorAll(`input[name="${this.primaryKeyValue}[]"]`)}input(t){return this.element.querySelector(`input[name="${this.primaryKeyValue}[]"][value="${t}"]`)}countValueChanged(t){this.element.toggleAttribute("hidden",0===t),this.countTarget.textContent=t,this.singularTarget.toggleAttribute("hidden",1!==t),this.pluralTarget.toggleAttribute("hidden",1===t)}}},{identifier:"tables--selection--item",controllerConstructor:i}];export{r as default};
1
+ import{Controller as t}from"@hotwired/stimulus";class e{constructor(t,e,s){this.cursorOffset=e.offsetY,this.initialPosition=e.target.offsetTop-t.offsetTop,this.targetId=s}updateCursor(t,e,s,i){this.listOffset=t.getBoundingClientRect().top;let r=s.clientY-this.listOffset-this.cursorOffset;this.#t(t,e,r,i)}updateScroll(t,e,s){const i=this.listOffset;this.listOffset=t.getBoundingClientRect().top;const r=i-this.listOffset,a=this.position+r;this.#t(t,e,a,s)}#t(t,e,s,i){s=Math.max(s,0),s=Math.min(s,t.offsetHeight-e.offsetHeight),this.position=s;i(s-this.initialPosition)}}class s extends t{static outlets=["tables--selection--form"];static values={params:Object,checked:Boolean};tablesSelectionFormOutletConnected(t){this.checkedValue=t.isSelected(this.id)}change(t){t.preventDefault(),this.checkedValue=this.tablesSelectionFormOutlet.toggle(this.id)}get id(){return this.paramsValue.id}checkedValueChanged(t){this.element.querySelector("input").checked=t}}const i=[{identifier:"tables--orderable--item",controllerConstructor:class extends t{static values={params:Object};connect(){var t;this.index=(t=this.row,Array.from(t.parentElement.children).indexOf(t))}paramsValueChanged(t){this.id=t.id_value}dragUpdate(t){this.dragOffset=t,this.row.style.position="relative",this.row.style.top=t+"px",this.row.style.zIndex="1",this.row.toggleAttribute("dragging",!0)}updateVisually(t){this.row.style.position="relative",this.row.style.top=this.row.offsetHeight*(t-this.dragIndex)+"px"}updateIndex(t){this.index=t}params(t){const{id_name:e,id_value:s,index_name:i}=this.paramsValue;return[{name:`${t}[${s}][${e}]`,value:this.id},{name:`${t}[${s}][${i}]`,value:this.index}]}reset(){delete this.dragOffset,this.row.removeAttribute("style"),this.row.removeAttribute("dragging")}get hasChanges(){return this.paramsValue.index_value!==this.index}get dragIndex(){return this.dragOffset&&0!==this.dragOffset?this.index+Math.round(this.dragOffset/this.row.offsetHeight):this.index}get comparisonIndex(){return this.dragOffset?this.dragIndex+(this.dragOffset>0?.5:-.5):this.index}get row(){return this.element.parentElement}}},{identifier:"tables--orderable--list",controllerConstructor:class extends t{static outlets=["tables--orderable--item","tables--orderable--form"];startDragging(t){this.dragState=t,document.addEventListener("mousemove",this.mousemove),document.addEventListener("mouseup",this.mouseup),window.addEventListener("scroll",this.scroll,!0),this.element.style.position="relative"}stopDragging(){const t=this.dragState;return delete this.dragState,document.removeEventListener("mousemove",this.mousemove),document.removeEventListener("mouseup",this.mouseup),window.removeEventListener("scroll",this.scroll,!0),this.element.removeAttribute("style"),this.tablesOrderableItemOutlets.forEach((t=>t.reset())),t}drop(){const t=this.dragItem;if(!t)return;const e=t.dragIndex,s=this.tablesOrderableItemOutlets[e];s&&(e<t.index?s.row.insertAdjacentElement("beforebegin",t.row):e>t.index&&s.row.insertAdjacentElement("afterend",t.row),this.tablesOrderableItemOutlets.forEach(((t,e)=>t.updateIndex(e))),this.commitChanges())}commitChanges(){this.tablesOrderableFormOutlet.clear(),this.tablesOrderableItemOutlets.forEach((t=>{t.hasChanges&&this.tablesOrderableFormOutlet.add(t)})),this.tablesOrderableFormOutlet.submit()}mousedown(t){if(this.isDragging)return;const s=this.#e(t.target);s&&(t.preventDefault(),this.startDragging(new e(this.element,t,s.id)),this.dragState.updateCursor(this.element,s.row,t,this.animate))}mousemove=t=>{this.isDragging&&(t.preventDefault(),this.ticking||(this.ticking=!0,window.requestAnimationFrame((()=>{this.ticking=!1,this.dragState.updateCursor(this.element,this.dragItem.row,t,this.animate)}))))};scroll=t=>{this.isDragging&&!this.ticking&&(this.ticking=!0,window.requestAnimationFrame((()=>{this.ticking=!1,this.dragState.updateScroll(this.element,this.dragItem.row,this.animate)})))};mouseup=t=>{this.isDragging&&(this.drop(),this.stopDragging(),this.tablesOrderableFormOutlets.forEach((t=>delete t.dragState)))};tablesOrderableFormOutletConnected(t,e){t.dragState&&this.startDragging(t.dragState)}tablesOrderableFormOutletDisconnected(t,e){this.isDragging&&(t.dragState=this.stopDragging())}animate=t=>{const e=this.dragItem;e.dragUpdate(t),this.#s.forEach(((t,s)=>{t!==e&&t.updateVisually(s)}))};get isDragging(){return!!this.dragState}get dragItem(){return this.isDragging?this.tablesOrderableItemOutlets.find((t=>t.id===this.dragState.targetId)):null}get#s(){return this.tablesOrderableItemOutlets.toSorted(((t,e)=>t.comparisonIndex-e.comparisonIndex))}#e(t){return this.tablesOrderableItemOutlets.find((e=>e.element===t))}}},{identifier:"tables--orderable--form",controllerConstructor:class extends t{static values={scope:String};add(t){t.params(this.scopeValue).forEach((({name:t,value:e})=>{this.element.insertAdjacentHTML("beforeend",`<input type="hidden" name="${t}" value="${e}" data-generated>`)}))}submit(){0!==this.inputs.length&&this.element.requestSubmit()}clear(){this.inputs.forEach((t=>t.remove()))}get inputs(){return this.element.querySelectorAll("input[data-generated]")}}},{identifier:"tables--selection--form",controllerConstructor:class extends t{static values={count:Number,primaryKey:{type:String,default:"id"}};static targets=["count","singular","plural"];connect(){this.countValue=this.inputs.length}toggle(t){const e=this.input(t);return e?e.remove():this.element.insertAdjacentHTML("beforeend",`<input type="hidden" name="${this.primaryKeyValue}[]" value="${t}">`),this.countValue=this.inputs.length,!e}isSelected(t){return!!this.input(t)}get inputs(){return this.element.querySelectorAll(`input[name="${this.primaryKeyValue}[]"]`)}input(t){return this.element.querySelector(`input[name="${this.primaryKeyValue}[]"][value="${t}"]`)}countValueChanged(t){this.element.toggleAttribute("hidden",0===t),this.countTarget.textContent=t,this.singularTarget.toggleAttribute("hidden",1!==t),this.pluralTarget.toggleAttribute("hidden",1===t)}}},{identifier:"tables--selection--item",controllerConstructor:s}];export{i as default};
2
2
  //# sourceMappingURL=tables.min.js.map