katalyst-tables 3.0.0.beta1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -2
  3. data/README.md +65 -187
  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/assets/stylesheets/katalyst/tables/_index.scss +1 -0
  9. data/app/assets/stylesheets/katalyst/tables/_summary.scss +14 -0
  10. data/app/assets/stylesheets/katalyst/tables/typed-columns/_boolean.scss +1 -1
  11. data/app/assets/stylesheets/katalyst/tables/typed-columns/_currency.scss +3 -0
  12. data/app/assets/stylesheets/katalyst/tables/typed-columns/_date.scss +1 -1
  13. data/app/assets/stylesheets/katalyst/tables/typed-columns/_datetime.scss +1 -1
  14. data/app/assets/stylesheets/katalyst/tables/typed-columns/_number.scss +3 -0
  15. data/app/components/concerns/katalyst/tables/has_table_content.rb +19 -10
  16. data/app/components/concerns/katalyst/tables/identifiable.rb +51 -0
  17. data/app/components/concerns/katalyst/tables/orderable.rb +35 -105
  18. data/app/components/concerns/katalyst/tables/row_renderer.rb +1 -1
  19. data/app/components/concerns/katalyst/tables/selectable.rb +18 -75
  20. data/app/components/concerns/katalyst/tables/sortable.rb +51 -17
  21. data/app/components/katalyst/summary_table_component.html.erb +15 -0
  22. data/app/components/katalyst/summary_table_component.rb +44 -0
  23. data/app/components/katalyst/table_component.html.erb +4 -4
  24. data/app/components/katalyst/table_component.rb +271 -53
  25. data/app/components/katalyst/tables/body_row_component.html.erb +5 -0
  26. data/app/components/katalyst/tables/body_row_component.rb +4 -31
  27. data/app/components/katalyst/tables/cell_component.rb +85 -0
  28. data/app/components/katalyst/tables/{body → cells}/boolean_component.rb +8 -2
  29. data/app/components/katalyst/tables/{body → cells}/currency_component.rb +7 -7
  30. data/app/components/katalyst/tables/{body → cells}/date_component.rb +12 -9
  31. data/app/components/katalyst/tables/{body → cells}/date_time_component.rb +13 -10
  32. data/app/components/katalyst/tables/{body → cells}/number_component.rb +5 -5
  33. data/app/components/katalyst/tables/cells/ordinal_component.rb +44 -0
  34. data/app/components/katalyst/tables/{body → cells}/rich_text_component.rb +8 -5
  35. data/app/components/katalyst/tables/cells/select_component.rb +39 -0
  36. data/app/components/katalyst/tables/data.rb +30 -0
  37. data/app/components/katalyst/tables/header_row_component.html.erb +5 -0
  38. data/app/components/katalyst/tables/header_row_component.rb +4 -25
  39. data/app/components/katalyst/tables/label.rb +37 -0
  40. data/app/components/katalyst/tables/orderable/form_component.rb +38 -0
  41. data/app/components/katalyst/tables/selectable/form_component.html.erb +3 -3
  42. data/app/components/katalyst/tables/selectable/form_component.rb +8 -11
  43. data/app/components/katalyst/tables/summary/body_component.html.erb +3 -0
  44. data/app/components/katalyst/tables/summary/body_component.rb +10 -0
  45. data/app/components/katalyst/tables/summary/header_component.html.erb +3 -0
  46. data/app/components/katalyst/tables/summary/header_component.rb +10 -0
  47. data/app/components/katalyst/tables/summary/row_component.html.erb +4 -0
  48. data/app/components/katalyst/tables/summary/row_component.rb +12 -0
  49. data/app/controllers/concerns/katalyst/tables/backend.rb +17 -28
  50. data/app/helpers/katalyst/tables/frontend.rb +67 -2
  51. data/app/javascript/tables/application.js +0 -5
  52. data/app/javascript/tables/orderable/form_controller.js +8 -6
  53. data/app/javascript/tables/orderable/item_controller.js +9 -0
  54. data/app/models/concerns/katalyst/tables/collection/core.rb +6 -1
  55. data/app/models/concerns/katalyst/tables/collection/pagination.rb +2 -2
  56. data/app/models/concerns/katalyst/tables/collection/sorting.rb +86 -18
  57. data/app/models/katalyst/tables/collection/array.rb +38 -0
  58. data/app/models/katalyst/tables/collection/base.rb +4 -0
  59. data/app/models/katalyst/tables/collection/filter.rb +2 -2
  60. data/config/importmap.rb +1 -0
  61. data/config/locales/tables.en.yml +0 -6
  62. data/lib/katalyst/tables/config.rb +23 -0
  63. data/lib/katalyst/tables.rb +9 -0
  64. metadata +32 -30
  65. data/app/components/concerns/katalyst/tables/body/typed_columns.rb +0 -132
  66. data/app/components/concerns/katalyst/tables/configurable_component.rb +0 -52
  67. data/app/components/concerns/katalyst/tables/header/typed_columns.rb +0 -179
  68. data/app/components/katalyst/tables/body/attachment_component.rb +0 -58
  69. data/app/components/katalyst/tables/body/link_component.rb +0 -40
  70. data/app/components/katalyst/tables/body_cell_component.rb +0 -55
  71. data/app/components/katalyst/tables/header/attachment_component.rb +0 -15
  72. data/app/components/katalyst/tables/header/boolean_component.rb +0 -15
  73. data/app/components/katalyst/tables/header/currency_component.rb +0 -15
  74. data/app/components/katalyst/tables/header/date_component.rb +0 -15
  75. data/app/components/katalyst/tables/header/date_time_component.rb +0 -15
  76. data/app/components/katalyst/tables/header/link_component.rb +0 -15
  77. data/app/components/katalyst/tables/header/number_component.rb +0 -15
  78. data/app/components/katalyst/tables/header/rich_text_component.rb +0 -15
  79. data/app/components/katalyst/tables/header_cell_component.rb +0 -97
  80. data/app/helpers/katalyst/tables/frontend/helper.rb +0 -31
  81. data/app/javascript/tables/turbo/collection_controller.js +0 -38
  82. 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: 196a8b7ff382215cbb156ff83099071aa983176353f819f5031aac774a88a572
4
+ data.tar.gz: 3d7463ccb20935e52500f186fb7c4024f3c8c236bfb9338a331a0830bf58edb5
5
5
  SHA512:
6
- metadata.gz: ea6efd6150d3bb2d9f9d48c89920831f97e6dede515cfcd99ec049f81328c32c373fbf49cfd798adde0f1d8055819e1eb4f40931f8ac392809006c384c219c98
7
- data.tar.gz: 66480c79be3aa1762145c3d92723f284ed61749d46029600d20541820e2d831e266fa79ffa0de9ad77efc750ba80f274f7fee86267b7a946cad78ba8c49773da
6
+ metadata.gz: 325bfa1b161fa322acd419279a57ddb644f5a3e0289ca42294c9936fe7ea6fcded6f1d077f8866b5b70408d7d495c8dabe4733ba83a1f7652c4eb02115bc5438
7
+ data.tar.gz: 1382995a15fdc57829232223d67d20a691b9f5b908f4059c7b41dcfdb9396ce1d90f3431417ceb2b88a10eca880939608b20ec19dc421288f25b8c6bfad0c58c
data/CHANGELOG.md CHANGED
@@ -1,8 +1,22 @@
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.1.0]
2
+ - Introduce summary tables
3
+ - Update ruby requirement >= 3.3
4
+
5
+ ## [3.0.0]
6
+
7
+ - Breaking change: remove Turbo Streams from table and pagination components,
8
+ focus preservation is handled via Turbo Morph
3
9
  - Improve spec coverage
10
+ - Re-write internals to make it easier to extend and customize
4
11
  - Update examples in [README](README.md) and [docs](/docs) to reflect changes
5
12
 
13
+ If you're upgrading from 2.x, you'll need to change your controllers to use
14
+ the recommendations from the README. The changes should be straightforward,
15
+ but you will need to enable morphing to allow focus preservation.
16
+
17
+ In general, we don't recommend using row partials anymore, as it's easier to
18
+ read the code when the row is defined in the index view.
19
+
6
20
  ## [2.6.0]
7
21
 
8
22
  - Added table row selection
data/README.md CHANGED
@@ -24,33 +24,49 @@ 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.
32
- * `Katalyst::Tables::Frontend` provides `table_with` for inline table generation
35
+ * `Katalyst::TableComponent` can be used render encapsulated tables,
36
+ * `Katalyst::SummaryTableComponent` can be used render a record using the table syntax,
37
+ * `Katalyst::Tables::Frontend` provides `table_with` for inline table generation,
33
38
  * `Katalyst::Tables::Collection::Base` provides a default entry point for
34
- building collections in your controller actions.
39
+ building collections in your controller actions
35
40
 
36
- ## Frontend
41
+ ### Frontend
37
42
 
38
- Use `Katalyst::TableComponent` to build a table component from an ActiveRecord
39
- collection, or from a `Katalyst::Tables::Collection::Base` instance.
43
+ Add `include Katalyst::Tables::Frontend` to your `ApplicationHelper` or similar.
40
44
 
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).
45
+ You can use the `table_with` helper to generate a table inline in your view without explicitly interacting with the
46
+ table component. This is the preferred approach when creating tables. However, if the table is complex or you need to
47
+ reuse it, you should consider moving the definition of the row into a partial.
48
+
49
+ ```erb
50
+ <%= table_with collection: @people do |row, person| %>
51
+ <% row.text :name do |cell| %>
52
+ <%= link_to cell.value, [:edit, person] %>
53
+ <% end %>
54
+ <% row.text :email %>
55
+ <% end %>
56
+ ```
57
+
58
+ By not providing a block to the `table_with` call, the gem will look for a partial called `_person.html+row.erb` to
59
+ render each row:
44
60
 
45
61
  ```erb
46
62
  <%# locals: { row:, person: nil } %>
47
- <% row.cell :name do |cell| %>
63
+ <% row.text :name do |cell| %>
48
64
  <%= link_to cell.value, [:edit, person] %>
49
65
  <% end %>
50
- <% row.cell :email %>
66
+ <% row.text :email %>
51
67
  ```
52
68
 
53
- The table component will call your partial once per row and accumulate the cells
69
+ The table will call your block / partial once per row and accumulate the cells
54
70
  you generate into rows, including a header row:
55
71
 
56
72
  ```html
@@ -79,52 +95,36 @@ You can customize the partial and/or the name of the resource in a similar style
79
95
  to view partials:
80
96
 
81
97
  ```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 %>
98
+ <%= table_with(collection: @employees, as: :person, partial: "person") %>
99
99
  ```
100
100
 
101
101
  ### HTML Attributes
102
102
 
103
103
  You can add custom attributes on table, row, and cell tags.
104
104
 
105
- The table tag takes attributes passed to `TableComponent` or via the call to `table_with`, similar to `form_with`:
105
+ The table tag takes attributes passed to `table_with` helper, similar to `form_with`:
106
106
 
107
107
  ```erb
108
- <%= TableComponent.new(collection: @people, id: "people-table")
108
+ <%= table_with(collection: @people, id: "people-table")
109
109
  ```
110
110
 
111
111
  Cells support the same approach:
112
112
 
113
113
  ```erb
114
- <%= row.cell :name, class: "name" %>
114
+ <%= row.text :name, class: "name" %>
115
115
  ```
116
116
 
117
117
  Rows do not get called directly, so instead you can assign to `html_attributes` on the row builder to customize row tag
118
118
  generation.
119
119
 
120
120
  ```erb
121
- <% row.html_attributes = { id: person.id } if row.body? %>
121
+ <% row.update_html_attributes(id: person.id) if row.body? %>
122
122
  ```
123
123
 
124
124
  Note: because the row builder gets called to generate the header row, you may need to guard calls that access the
125
125
  `person` directly as shown in the previous example. You could also check whether `person` is present.
126
126
 
127
- #### Headers
127
+ ### Headers
128
128
 
129
129
  Tables will automatically generate a header row for you by calling your row partial or provided block with no object.
130
130
  During this call, `row.header?` is true, `row.body?` is false, and the object (`person`) is nil.
@@ -133,14 +133,14 @@ All cells generated in the table header iteration will automatically be header c
133
133
  in your body rows by passing `heading: true` when you generate the cell.
134
134
 
135
135
  ```erb
136
- <% row.cell :id, heading: true %>
136
+ <% row.number :id, heading: true %>
137
137
  ```
138
138
 
139
139
  The table header cells default to showing the capitalized column name, but you can customize this in one of two ways:
140
140
 
141
141
  * Set the value inline
142
142
  ```erb
143
- <% row.cell :id, label: "ID" %>
143
+ <% row.number :id, label: "ID" %>
144
144
  ```
145
145
  * Define a translation for the attribute
146
146
  ```yml
@@ -163,194 +163,72 @@ the table cell. This is often all you need to do, but if you do want to customis
163
163
  the value you can pass a block instead:
164
164
 
165
165
  ```erb
166
- <% row.cell :status do %>
166
+ <% row.text :status do %>
167
167
  <%= person.password.present? ? "Active" : "Invited" %>
168
168
  <% end %>
169
169
  ```
170
170
 
171
- In the context of the block you have access the cell builder if you simply
171
+ In the context of the block you have access the cell component if you simply
172
172
  want to extend the default behaviour:
173
173
 
174
174
  ```erb
175
- <% row.cell :status do |cell| %>
176
- <%= link_to cell.value, person %>
175
+ <%# @type [Katalyst::Tables::CellComponent] cell %>
176
+ <% row.text :name do |cell| %>
177
+ <%= link_to cell, person %>
177
178
  <% end %>
178
179
  ```
179
180
 
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.
181
+ You can also update `html_attributes` on the cell builder, similar to the row
182
+ builder, see `katalyst-html-attributes` for details.
183
183
 
184
184
  ## Collections
185
185
 
186
186
  The `Katalyst::Tables::Collection::Base` class provides a convenient way to
187
187
  manage collections in your controller actions. It is designed to be used with
188
188
  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.
189
+ collections. Sorting and Pagination are off by default, you can either set them
190
+ on creation or create a custom `Collection` class that sets them on by default:
191
191
 
192
192
  ```ruby
193
- class ApplicationCollection < Katalyst::Tables::Collection::Base
194
- config.sorting = "name" # requires models have a name attribute
193
+ # in #index
194
+ Katalyst::Tables::Collection::Base.new(sorting: "name asc", pagination: true)
195
+ # or as a nested class in your controller
196
+ class Collection < Katalyst::Tables::Collection::Base
197
+ config.sorting = "name asc" # requires models have a name attribute
195
198
  config.pagination = true
196
199
  end
197
200
  ```
198
201
 
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
202
+ Collections can be passed directly to `table_with` method and it will automatically
210
203
  detect features such as sorting and generate the appropriate table header links.
211
204
 
212
205
  ```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
206
+ <%= table_with(collection:) %>
233
207
  ```
234
208
 
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:
209
+ ## Summary tables
210
+ You can use the `Katalyst::SummaryTableComponent` to render a single record utilizing all the functionality from the
211
+ `Katalyst::TableComponent`.
255
212
 
256
213
  ```erb
257
- <%= table_with collection: @people, sort: @sort do |row, person| %>
258
- <%= row.cell :name %>
259
- <%= row.cell :email %>
214
+ <%= summary_table_with model: @person do |row| %>
215
+ <% row.text :name %>
216
+ <% row.text :email %>
260
217
  <% end %>
261
218
  ```
262
219
 
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) %>
275
- ```
276
-
277
220
  ## Extensions
278
221
 
279
- The following extensions are available:
222
+ The following extensions are available and activated by default:
280
223
 
224
+ * [Identifiable](docs/identifiable.md) - adds default dom ids to the table and data rows.
281
225
  * [Orderable](docs/orderable.md) - adds bulk-update for 'ordinal' columns via dragging rows in the table.
226
+ * [Pagination](docs/pagination.md) - handles paginating of data in the collection.
282
227
  * [Selectable](docs/selectable.md) - adds bulk-action support for rows in the table.
228
+ * [Sortable](docs/sortable.md) - table column headers that can be sorted will be wrapped in links.
229
+ * [Customization](docs/customization.md) - customize the table and cell rendering.
283
230
 
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
- ```
231
+ You can disable extensions by altering the `Katalyst::Tables.config.component_extensions` before initialization.
354
232
 
355
233
  ## Development
356
234
 
@@ -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