katalyst-tables 2.2.1 → 2.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/controllers/tables/orderable/form_controller.js +11 -5
- data/app/assets/javascripts/controllers/tables/orderable/item_controller.js +94 -2
- data/app/assets/javascripts/controllers/tables/orderable/list_controller.js +211 -37
- data/app/components/concerns/katalyst/tables/has_table_content.rb +1 -1
- data/app/components/concerns/katalyst/tables/orderable.rb +51 -35
- data/app/components/concerns/katalyst/tables/turbo_replaceable.rb +24 -7
- data/app/components/katalyst/turbo/table_component.rb +4 -4
- data/lib/katalyst/tables/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8224ebb910f428c3f7750ae6e79c92f21440f5546a2baaede473bb286f217a82
|
4
|
+
data.tar.gz: 6be02c8a263dda22b7faae734cb20e8cec5879db532adb50f257bd502fa51265
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61052d361a93ab2675274f9ed3b3f59e0acd08519261cf016c4db55006097d72f78114ba489e5eed9d066a82aa6bbaa9dd00675d378e24c996a5ccba775bce75
|
7
|
+
data.tar.gz: e411c014472cd5d34338ad0a02d869d69352fd4c6abe70c68ead05d75b3d2f0f52bf11c263031cd6606d27d09c7565e76770eee0b9149b58ef5e91ec383d86bf
|
@@ -1,20 +1,26 @@
|
|
1
1
|
import { Controller } from "@hotwired/stimulus";
|
2
2
|
|
3
3
|
export default class OrderableFormController extends Controller {
|
4
|
-
add(
|
4
|
+
add(item) {
|
5
|
+
const { id_name, id_value, index_name } = item.paramsValue;
|
5
6
|
this.element.insertAdjacentHTML(
|
6
7
|
"beforeend",
|
7
|
-
`<input type="hidden" name="${
|
8
|
+
`<input type="hidden" name="${id_name}" value="${id_value}" data-generated>
|
9
|
+
<input type="hidden" name="${index_name}" value="${item.index}" data-generated>`,
|
8
10
|
);
|
9
11
|
}
|
10
12
|
|
11
13
|
submit() {
|
14
|
+
if (this.inputs.length === 0) return;
|
15
|
+
|
12
16
|
this.element.requestSubmit();
|
13
17
|
}
|
14
18
|
|
15
19
|
clear() {
|
16
|
-
this.
|
17
|
-
|
18
|
-
|
20
|
+
this.inputs.forEach((input) => input.remove());
|
21
|
+
}
|
22
|
+
|
23
|
+
get inputs() {
|
24
|
+
return this.element.querySelectorAll("input[data-generated]");
|
19
25
|
}
|
20
26
|
}
|
@@ -2,7 +2,99 @@ import { Controller } from "@hotwired/stimulus";
|
|
2
2
|
|
3
3
|
export default class OrderableRowController extends Controller {
|
4
4
|
static values = {
|
5
|
-
|
6
|
-
value: Number,
|
5
|
+
params: Object,
|
7
6
|
};
|
7
|
+
|
8
|
+
paramsValueChanged(params) {
|
9
|
+
this.id = params.id_value;
|
10
|
+
this.index = params.index_value;
|
11
|
+
}
|
12
|
+
|
13
|
+
dragUpdate(offset) {
|
14
|
+
this.dragOffset = offset;
|
15
|
+
this.row.style.position = "relative";
|
16
|
+
this.row.style.top = offset + "px";
|
17
|
+
this.row.style.zIndex = 1;
|
18
|
+
this.row.toggleAttribute("dragging", true);
|
19
|
+
}
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Called on items that are not the dragged item during drag. Updates the
|
23
|
+
* visual position of the item relative to the dragged item.
|
24
|
+
*
|
25
|
+
* @param index {number} intended index of the item during drag
|
26
|
+
*/
|
27
|
+
updateVisually(index) {
|
28
|
+
this.row.style.position = "relative";
|
29
|
+
this.row.style.top = `${
|
30
|
+
this.row.offsetHeight * (index - this.dragIndex)
|
31
|
+
}px`;
|
32
|
+
}
|
33
|
+
|
34
|
+
/**
|
35
|
+
* Set the index value of the item. This is called on all items after a drop
|
36
|
+
* event. If the index is different to the params index then this item has
|
37
|
+
* changed.
|
38
|
+
*
|
39
|
+
* @param index {number} the new index value
|
40
|
+
*/
|
41
|
+
updateIndex(index) {
|
42
|
+
this.index = index;
|
43
|
+
}
|
44
|
+
|
45
|
+
/**
|
46
|
+
* Restore any visual changes made during drag and remove the drag state.
|
47
|
+
*/
|
48
|
+
reset() {
|
49
|
+
delete this.dragOffset;
|
50
|
+
this.row.removeAttribute("style");
|
51
|
+
this.row.removeAttribute("dragging");
|
52
|
+
}
|
53
|
+
|
54
|
+
/**
|
55
|
+
* @returns {boolean} true when the item has a change to its index value
|
56
|
+
*/
|
57
|
+
get hasChanges() {
|
58
|
+
return this.paramsValue.index_value !== this.index;
|
59
|
+
}
|
60
|
+
|
61
|
+
/**
|
62
|
+
* Calculate the relative index of the item during drag. This is used to
|
63
|
+
* sort items during drag as it takes into account any uncommitted changes
|
64
|
+
* to index caused by the drag offset.
|
65
|
+
*
|
66
|
+
* @returns {number} index for the purposes of drag and drop ordering
|
67
|
+
*/
|
68
|
+
get dragIndex() {
|
69
|
+
if (this.dragOffset && this.dragOffset !== 0) {
|
70
|
+
return this.index + Math.round(this.dragOffset / this.row.offsetHeight);
|
71
|
+
} else {
|
72
|
+
return this.index;
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
/**
|
77
|
+
* Index value for use in comparisons during drag. This is used to determine
|
78
|
+
* whether the dragged item is above or below another item. If this item is
|
79
|
+
* being dragged then we offset the index by 0.5 to ensure that it jumps up
|
80
|
+
* or down when it reaches the midpoint of the item above or below it.
|
81
|
+
*
|
82
|
+
* @returns {number}
|
83
|
+
*/
|
84
|
+
get comparisonIndex() {
|
85
|
+
if (this.dragOffset) {
|
86
|
+
return this.dragIndex + (this.dragOffset > 0 ? 0.5 : -0.5);
|
87
|
+
} else {
|
88
|
+
return this.index;
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
/**
|
93
|
+
* The containing row element.
|
94
|
+
*
|
95
|
+
* @returns {HTMLElement}
|
96
|
+
*/
|
97
|
+
get row() {
|
98
|
+
return this.element.parentElement;
|
99
|
+
}
|
8
100
|
}
|
@@ -3,71 +3,245 @@ import { Controller } from "@hotwired/stimulus";
|
|
3
3
|
export default class OrderableListController extends Controller {
|
4
4
|
static outlets = ["tables--orderable--item", "tables--orderable--form"];
|
5
5
|
|
6
|
-
|
7
|
-
if (this.element !== event.target.parentElement) return;
|
6
|
+
//region State transitions
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
startDragging(dragState) {
|
9
|
+
this.dragState = dragState;
|
11
10
|
|
12
|
-
|
13
|
-
|
11
|
+
document.addEventListener("mousemove", this.mousemove);
|
12
|
+
document.addEventListener("mouseup", this.mouseup);
|
13
|
+
|
14
|
+
this.element.style.position = "relative";
|
14
15
|
}
|
15
16
|
|
16
|
-
|
17
|
-
|
17
|
+
stopDragging() {
|
18
|
+
const dragState = this.dragState;
|
19
|
+
delete this.dragState;
|
18
20
|
|
19
|
-
|
21
|
+
document.removeEventListener("mousemove", this.mousemove);
|
22
|
+
document.removeEventListener("mouseup", this.mouseup);
|
20
23
|
|
21
|
-
|
22
|
-
|
23
|
-
}
|
24
|
+
this.element.removeAttribute("style");
|
25
|
+
this.tablesOrderableItemOutlets.forEach((item) => item.reset());
|
24
26
|
|
25
|
-
|
26
|
-
event.preventDefault();
|
27
|
+
return dragState;
|
27
28
|
}
|
28
29
|
|
29
|
-
drop(
|
30
|
-
|
30
|
+
drop() {
|
31
|
+
// note: early returns guard against turbo updates that prevent us finding
|
32
|
+
// the right item to drop on. In this situation it's better to discard the
|
33
|
+
// drop than to drop in the wrong place.
|
34
|
+
|
35
|
+
const dragItem = this.dragItem;
|
36
|
+
|
37
|
+
if (!dragItem) return;
|
38
|
+
|
39
|
+
const newIndex = dragItem.dragIndex;
|
40
|
+
const targetItem = this.tablesOrderableItemOutlets[newIndex];
|
41
|
+
|
42
|
+
if (!targetItem) return;
|
31
43
|
|
32
|
-
|
33
|
-
|
44
|
+
// swap the dragged item into the correct position for its current offset
|
45
|
+
if (newIndex < dragItem.index) {
|
46
|
+
targetItem.row.insertAdjacentElement("beforebegin", dragItem.row);
|
47
|
+
} else if (newIndex > dragItem.index) {
|
48
|
+
targetItem.row.insertAdjacentElement("afterend", dragItem.row);
|
49
|
+
}
|
50
|
+
|
51
|
+
// reindex all items based on their new positions
|
52
|
+
this.tablesOrderableItemOutlets.forEach((item, index) =>
|
53
|
+
item.updateIndex(index),
|
54
|
+
);
|
34
55
|
|
35
|
-
|
56
|
+
// save the changes
|
57
|
+
this.commitChanges();
|
36
58
|
}
|
37
59
|
|
38
|
-
|
60
|
+
commitChanges() {
|
39
61
|
// clear any existing inputs to prevent duplicates
|
40
62
|
this.tablesOrderableFormOutlet.clear();
|
41
63
|
|
42
64
|
// insert any items that have changed position
|
43
|
-
this.tablesOrderableItemOutlets.forEach((item
|
44
|
-
if (item.
|
45
|
-
this.tablesOrderableFormOutlet.add(item.nameValue, index);
|
46
|
-
}
|
65
|
+
this.tablesOrderableItemOutlets.forEach((item) => {
|
66
|
+
if (item.hasChanges) this.tablesOrderableFormOutlet.add(item);
|
47
67
|
});
|
48
68
|
|
49
69
|
this.tablesOrderableFormOutlet.submit();
|
50
70
|
}
|
51
71
|
|
52
|
-
|
53
|
-
|
72
|
+
//endregion
|
73
|
+
|
74
|
+
//region Events
|
75
|
+
|
76
|
+
mousedown(event) {
|
77
|
+
if (this.isDragging) return;
|
78
|
+
|
79
|
+
const target = this.#targetItem(event.target);
|
80
|
+
|
81
|
+
if (!target) return;
|
82
|
+
|
83
|
+
event.preventDefault(); // prevent built-in drag
|
84
|
+
|
85
|
+
this.startDragging(new DragState(this.element, event, target.id));
|
86
|
+
|
87
|
+
this.#updateDragItem(event);
|
54
88
|
}
|
55
89
|
|
56
|
-
|
57
|
-
|
58
|
-
|
90
|
+
mousemove = (event) => {
|
91
|
+
if (!this.isDragging) return;
|
92
|
+
|
93
|
+
event.preventDefault(); // prevent build-in drag
|
94
|
+
|
95
|
+
const dragItem = this.#updateDragItem(event);
|
96
|
+
|
97
|
+
// Visually updates the position of all items in the list relative to the
|
98
|
+
// dragged item. No actual changes to orderings at this stage.
|
99
|
+
this.#currentItems.forEach((item, index) => {
|
100
|
+
if (item === dragItem) return;
|
101
|
+
item.updateVisually(index);
|
102
|
+
});
|
103
|
+
};
|
104
|
+
|
105
|
+
mouseup = (event) => {
|
106
|
+
if (!this.isDragging) return;
|
107
|
+
|
108
|
+
this.drop();
|
109
|
+
this.stopDragging();
|
110
|
+
this.tablesOrderableFormOutlets.forEach((form) => delete form.dragState);
|
111
|
+
};
|
112
|
+
|
113
|
+
tablesOrderableFormOutletConnected(form, element) {
|
114
|
+
if (form.dragState) {
|
115
|
+
// restore the previous controller's state
|
116
|
+
this.startDragging(form.dragState);
|
59
117
|
}
|
60
|
-
return $e;
|
61
118
|
}
|
119
|
+
|
120
|
+
tablesOrderableFormOutletDisconnected(form, element) {
|
121
|
+
if (this.isDragging) {
|
122
|
+
// cache drag state in the form
|
123
|
+
form.dragState = this.stopDragging();
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
//endregion
|
128
|
+
|
129
|
+
//region Helpers
|
130
|
+
|
131
|
+
get isDragging() {
|
132
|
+
return !!this.dragState;
|
133
|
+
}
|
134
|
+
|
135
|
+
get dragItem() {
|
136
|
+
if (!this.isDragging) return null;
|
137
|
+
|
138
|
+
return this.tablesOrderableItemOutlets.find(
|
139
|
+
(item) => item.id === this.dragState.targetId,
|
140
|
+
);
|
141
|
+
}
|
142
|
+
|
143
|
+
/**
|
144
|
+
* Returns the current items in the list, sorted by their current index.
|
145
|
+
* Current uses the drag index if the item is being dragged, if set.
|
146
|
+
*
|
147
|
+
* @returns {Array[OrderableRowController]}
|
148
|
+
*/
|
149
|
+
get #currentItems() {
|
150
|
+
return this.tablesOrderableItemOutlets.toSorted(
|
151
|
+
(a, b) => a.comparisonIndex - b.comparisonIndex,
|
152
|
+
);
|
153
|
+
}
|
154
|
+
|
155
|
+
/**
|
156
|
+
* Returns the item outlet that was clicked on, if any.
|
157
|
+
*
|
158
|
+
* @param event {HTMLElement} the clicked ordinal cell
|
159
|
+
* @returns {OrderableRowController}
|
160
|
+
*/
|
161
|
+
#targetItem(element) {
|
162
|
+
return this.tablesOrderableItemOutlets.find(
|
163
|
+
(item) => item.element === element,
|
164
|
+
);
|
165
|
+
}
|
166
|
+
|
167
|
+
#updateDragItem(event) {
|
168
|
+
const offset = this.dragState.itemOffset(
|
169
|
+
this.element,
|
170
|
+
this.dragItem.row,
|
171
|
+
event,
|
172
|
+
);
|
173
|
+
const item = this.dragItem;
|
174
|
+
item.dragUpdate(offset);
|
175
|
+
return item;
|
176
|
+
}
|
177
|
+
|
178
|
+
//endregion
|
62
179
|
}
|
63
180
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
181
|
+
/**
|
182
|
+
* During drag we want to be able to translate a document-relative coordinate
|
183
|
+
* into a coordinate relative to the list element. This state object calculates
|
184
|
+
* and stores internal state so that we can translate absolute page coordinates
|
185
|
+
* from mouse events into relative offsets for the list items within the list
|
186
|
+
* element.
|
187
|
+
*
|
188
|
+
* We also keep track of the drag target so that if the controller is attached
|
189
|
+
* to a new element during the drag we can continue after the turbo update.
|
190
|
+
*/
|
191
|
+
class DragState {
|
192
|
+
/**
|
193
|
+
* @param list {HTMLElement} the list controller's element (tbody)
|
194
|
+
* @param event {MouseEvent} the initial event
|
195
|
+
* @param id {String} the id of the element being dragged
|
196
|
+
*/
|
197
|
+
constructor(list, event, id) {
|
198
|
+
// calculate the offset top of the tbody element relative to offsetParent
|
199
|
+
const offsetParent = list.offsetParent;
|
200
|
+
let offsetTop = event.offsetY;
|
201
|
+
let current = event.target;
|
202
|
+
while (current && current !== offsetParent) {
|
203
|
+
offsetTop += current.offsetTop;
|
204
|
+
current = current.offsetParent;
|
71
205
|
}
|
206
|
+
|
207
|
+
// page offset is the offset of the tbody element relative to the page
|
208
|
+
this.pageOffset = event.pageY - offsetTop + list.offsetTop;
|
209
|
+
|
210
|
+
// cursor offset is the offset of the cursor relative to the drag item
|
211
|
+
this.cursorOffset = event.offsetY;
|
212
|
+
|
213
|
+
// initial offset is the offset position of the drag item at drag start
|
214
|
+
this.initialOffset = event.target.offsetTop - list.offsetTop;
|
215
|
+
|
216
|
+
// id of the item being dragged
|
217
|
+
this.targetId = id;
|
218
|
+
}
|
219
|
+
|
220
|
+
/**
|
221
|
+
* Calculates the offset of the drag item relative to its initial position.
|
222
|
+
*
|
223
|
+
* @param list {HTMLElement} the list controller's element (tbody)
|
224
|
+
* @param row {HTMLElement} the row being dragged
|
225
|
+
* @param event {MouseEvent} the current event
|
226
|
+
* @returns {number} relative offset for the item being dragged
|
227
|
+
*/
|
228
|
+
itemOffset(list, row, event) {
|
229
|
+
// cursor position relative to the list
|
230
|
+
const cursorPosition = event.pageY - this.pageOffset;
|
231
|
+
|
232
|
+
// item position relative to the list
|
233
|
+
let itemPosition = cursorPosition - this.cursorOffset;
|
234
|
+
|
235
|
+
// ensure itemPosition is within the bounds of the list (tbody)
|
236
|
+
itemPosition = Math.max(itemPosition, 0);
|
237
|
+
itemPosition = Math.min(itemPosition, list.offsetHeight - row.offsetHeight);
|
238
|
+
|
239
|
+
// Item has position: relative, so we want to calculate the amount to move
|
240
|
+
// the item relative to it's DOM position to represent how much it has been
|
241
|
+
// dragged by.
|
242
|
+
|
243
|
+
// Convert itemPosition from offset relative to list to offset relative to
|
244
|
+
// its position within the DOM (if it hadn't moved).
|
245
|
+
return itemPosition - this.initialOffset;
|
72
246
|
}
|
73
247
|
}
|
@@ -26,7 +26,7 @@ module Katalyst
|
|
26
26
|
def row_partial(row, record = nil)
|
27
27
|
partial = @partial || model_name&.param_key&.to_s
|
28
28
|
as = @as || model_name&.param_key&.to_sym
|
29
|
-
render(partial: partial, variants: [:row], locals: { as => record, row: row })
|
29
|
+
render(partial: partial, variants: [:row], formats: [:html], locals: { as => record, row: row })
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -13,32 +13,30 @@ module Katalyst
|
|
13
13
|
|
14
14
|
using HasHtmlAttributes
|
15
15
|
|
16
|
-
#
|
17
|
-
#
|
18
|
-
|
16
|
+
# Support for inclusion in a table component class
|
17
|
+
# Adds an `orderable` slot and component configuration
|
18
|
+
included do
|
19
19
|
# Add `orderable` slot to table component
|
20
|
-
|
21
|
-
|
20
|
+
config_component :orderable, default: "Katalyst::Tables::Orderable::FormComponent"
|
21
|
+
renders_one(:orderable, lambda do |**attrs|
|
22
22
|
orderable_component.new(table: self, **attrs)
|
23
23
|
end)
|
24
24
|
end
|
25
25
|
|
26
|
-
# Support for inclusion in a table component class
|
27
|
-
included do
|
28
|
-
Orderable.make_orderable(self)
|
29
|
-
end
|
30
|
-
|
31
26
|
# Support for extending a table component instance
|
27
|
+
# Adds methods to the table component instance
|
32
28
|
def self.extended(table)
|
33
|
-
|
29
|
+
table.extend(TableMethods)
|
30
|
+
|
31
|
+
# ensure row components support orderable column calls
|
32
|
+
table.send(:add_orderable_columns)
|
34
33
|
end
|
35
34
|
|
36
35
|
def initialize(**attributes)
|
37
36
|
super
|
38
37
|
|
39
|
-
#
|
40
|
-
|
41
|
-
body_row_component.include(BodyRow)
|
38
|
+
# ensure row components support orderable column calls
|
39
|
+
add_orderable_columns
|
42
40
|
end
|
43
41
|
|
44
42
|
def tbody_attributes
|
@@ -47,16 +45,38 @@ module Katalyst
|
|
47
45
|
super.merge_html(
|
48
46
|
{ data: { controller: LIST_CONTROLLER,
|
49
47
|
action: <<~ACTIONS.squish,
|
50
|
-
|
51
|
-
dragenter->#{LIST_CONTROLLER}#dragenter
|
52
|
-
dragover->#{LIST_CONTROLLER}#dragover
|
53
|
-
drop->#{LIST_CONTROLLER}#drop
|
48
|
+
mousedown->#{LIST_CONTROLLER}#mousedown
|
54
49
|
ACTIONS
|
55
50
|
"#{LIST_CONTROLLER}-#{FORM_CONTROLLER}-outlet" => "##{orderable.id}",
|
56
51
|
"#{LIST_CONTROLLER}-#{ITEM_CONTROLLER}-outlet" => "td.ordinal" } },
|
57
52
|
)
|
58
53
|
end
|
59
54
|
|
55
|
+
private
|
56
|
+
|
57
|
+
# Add `orderable` columns to row components
|
58
|
+
def add_orderable_columns
|
59
|
+
header_row_component.include(HeaderRow)
|
60
|
+
body_row_component.include(BodyRow)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Methods required to emulate a slot when extending an existing table.
|
64
|
+
module TableMethods
|
65
|
+
def with_orderable(**attrs)
|
66
|
+
@orderable = FormComponent.new(table: self, **attrs)
|
67
|
+
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
def orderable?
|
72
|
+
@orderable.present?
|
73
|
+
end
|
74
|
+
|
75
|
+
def orderable
|
76
|
+
@orderable
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
60
80
|
module HeaderRow # :nodoc:
|
61
81
|
def ordinal(attribute = :ordinal, **)
|
62
82
|
cell(attribute, class: "ordinal", label: "")
|
@@ -64,23 +84,19 @@ module Katalyst
|
|
64
84
|
end
|
65
85
|
|
66
86
|
module BodyRow # :nodoc:
|
67
|
-
def ordinal(attribute = :ordinal,
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
87
|
+
def ordinal(attribute = :ordinal, primary_key: :id)
|
88
|
+
id = @record.public_send(primary_key)
|
89
|
+
params = {
|
90
|
+
id_name: @table.orderable.record_scope(id, primary_key),
|
91
|
+
id_value: id,
|
92
|
+
index_name: @table.orderable.record_scope(id, attribute),
|
93
|
+
index_value: @record.public_send(attribute),
|
94
|
+
}
|
95
|
+
cell(attribute, class: "ordinal", draggable: true, data: {
|
96
|
+
controller: ITEM_CONTROLLER,
|
97
|
+
"#{ITEM_CONTROLLER}-params-value": params.to_json,
|
74
98
|
}) { t("katalyst.tables.orderable.value") }
|
75
99
|
end
|
76
|
-
|
77
|
-
def html_attributes
|
78
|
-
super.merge_html(
|
79
|
-
{
|
80
|
-
draggable: "true",
|
81
|
-
},
|
82
|
-
)
|
83
|
-
end
|
84
100
|
end
|
85
101
|
|
86
102
|
class FormComponent < ViewComponent::Base # :nodoc:
|
@@ -98,8 +114,8 @@ module Katalyst
|
|
98
114
|
@scope = scope
|
99
115
|
end
|
100
116
|
|
101
|
-
def record_scope(
|
102
|
-
"#{scope}[#{
|
117
|
+
def record_scope(id, attribute)
|
118
|
+
"#{scope}[#{id}][#{attribute}]"
|
103
119
|
end
|
104
120
|
|
105
121
|
def call
|
@@ -13,10 +13,16 @@ module Katalyst
|
|
13
13
|
|
14
14
|
include ::Turbo::StreamsHelper
|
15
15
|
|
16
|
+
# Is turbo rendering enabled for this component?
|
16
17
|
def turbo?
|
17
18
|
@turbo
|
18
19
|
end
|
19
20
|
|
21
|
+
# Are we rendering a turbo stream response?
|
22
|
+
def turbo_stream_response?
|
23
|
+
response.media_type.eql?("text/vnd.turbo-stream.html")
|
24
|
+
end
|
25
|
+
|
20
26
|
def initialize(turbo: true, **options)
|
21
27
|
super(**options)
|
22
28
|
|
@@ -44,16 +50,27 @@ module Katalyst
|
|
44
50
|
super
|
45
51
|
|
46
52
|
redefinition_lock.synchronize do
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
53
|
+
# Capture the instance method added by the default compiler and
|
54
|
+
# wrap it in a turbo stream replacement. Take care to ensure that
|
55
|
+
# subclasses of this component don't break delegation, as each
|
56
|
+
# subclass of ViewComponent::Base defines its own version of this
|
57
|
+
# method.
|
58
|
+
vc_render_template = component_class.instance_method(:render_template_for)
|
59
|
+
component_class.define_method(:render_template_for) do |variant = nil|
|
60
|
+
# VC discards the output from this method and uses the buffer
|
61
|
+
# if both are set. Capture and wrap the output.
|
62
|
+
content = capture { vc_render_template.bind_call(self, variant) }
|
63
|
+
# In turbo mode, replace the inner-most element using a turbo
|
64
|
+
# stream. Note that we only want one turbo stream per component
|
65
|
+
# from this mechanism, as subclasses may want to concat their
|
66
|
+
# own additional streams.
|
67
|
+
if turbo? && turbo_stream_response? && !@streamed
|
68
|
+
@streamed = true
|
69
|
+
concat(turbo_stream.replace(id, content))
|
52
70
|
else
|
53
|
-
|
71
|
+
concat(content)
|
54
72
|
end
|
55
73
|
end
|
56
|
-
RUBY
|
57
74
|
end
|
58
75
|
end
|
59
76
|
end
|
@@ -7,6 +7,8 @@ module Katalyst
|
|
7
7
|
class TableComponent < ::Katalyst::TableComponent
|
8
8
|
include Tables::TurboReplaceable
|
9
9
|
|
10
|
+
attr_reader :id
|
11
|
+
|
10
12
|
def initialize(collection:, id:, header: true, **options)
|
11
13
|
header = if header.is_a?(Hash)
|
12
14
|
default_header_options.merge(header)
|
@@ -14,11 +16,9 @@ module Katalyst
|
|
14
16
|
default_header_options
|
15
17
|
end
|
16
18
|
|
17
|
-
|
18
|
-
end
|
19
|
+
@id = id
|
19
20
|
|
20
|
-
|
21
|
-
html_attributes[:id]
|
21
|
+
super(collection: collection, header: header, id: id, **options)
|
22
22
|
end
|
23
23
|
|
24
24
|
private
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: katalyst-tables
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.2.
|
4
|
+
version: 2.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katalyst Interactive
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-09-
|
11
|
+
date: 2023-09-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: html-attributes-utils
|