katalyst-tables 2.3.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/app/assets/builds/katalyst/tables.esm.js +8 -8
- data/app/assets/builds/katalyst/tables.js +8 -8
- data/app/assets/builds/katalyst/tables.min.js +1 -1
- data/app/assets/builds/katalyst/tables.min.js.map +1 -1
- data/app/components/concerns/katalyst/tables/configurable_component.rb +3 -3
- data/app/components/concerns/katalyst/tables/has_table_content.rb +17 -3
- data/app/components/katalyst/table_component.rb +2 -1
- data/app/components/katalyst/turbo/table_component.rb +3 -3
- data/{lib → app/controllers/concerns}/katalyst/tables/backend.rb +4 -5
- data/{lib → app/helpers}/katalyst/tables/frontend.rb +0 -2
- data/app/javascript/tables/application.js +2 -2
- data/app/javascript/tables/orderable/form_controller.js +1 -1
- data/app/javascript/tables/orderable/list_controller.js +6 -6
- data/app/models/concerns/katalyst/tables/collection/core.rb +2 -29
- data/app/models/concerns/katalyst/tables/collection/filtering.rb +23 -0
- data/app/models/concerns/katalyst/tables/collection/has_params.rb +43 -0
- data/app/models/concerns/katalyst/tables/collection/sorting.rb +2 -2
- data/app/models/katalyst/tables/{collection.rb → collection/base.rb} +27 -1
- data/app/models/katalyst/tables/collection/filter.rb +80 -0
- data/{lib/katalyst/tables/backend → app/models/katalyst/tables/collection}/sort_form.rb +13 -5
- data/lib/katalyst/tables.rb +0 -4
- metadata +17 -14
- /data/{lib → app/helpers}/katalyst/tables/frontend/helper.rb +0 -0
- /data/app/javascript/tables/turbo/{turbo_collection_controller.js → collection_controller.js} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5324d0bccadf307f48cb2199b8ba9123cb519756dda81cfc0c4c88a4abdfd886
|
4
|
+
data.tar.gz: ad299f1c8f62454aa3433b00cf4c0b69dacfe59cd6cfe598ab3ea2969cedecde
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd4daa1c09acaab6e85cbf787cfafb51303c9f030f26f15ca5f0a593da02c5dd526bf73d07b5cf6a99bfc267f2ea2a17d837e5eae81d607df2ad7788a9a99260
|
7
|
+
data.tar.gz: 143552907f2bf01ad256fc6e24b06404db1f173fe2ce3f4dca802eacd903fc5ab2f950a7c8c9ff6b086efe00c8838b8eca9dfbb2edcc0cc61d76717f6ca171ac
|
data/CHANGELOG.md
CHANGED
@@ -198,7 +198,7 @@ class OrderableListController extends Controller {
|
|
198
198
|
|
199
199
|
// reindex all items based on their new positions
|
200
200
|
this.tablesOrderableItemOutlets.forEach((item, index) =>
|
201
|
-
item.updateIndex(index)
|
201
|
+
item.updateIndex(index),
|
202
202
|
);
|
203
203
|
|
204
204
|
// save the changes
|
@@ -250,7 +250,7 @@ class OrderableListController extends Controller {
|
|
250
250
|
this.element,
|
251
251
|
this.dragItem.row,
|
252
252
|
event,
|
253
|
-
this.animate
|
253
|
+
this.animate,
|
254
254
|
);
|
255
255
|
});
|
256
256
|
};
|
@@ -265,7 +265,7 @@ class OrderableListController extends Controller {
|
|
265
265
|
this.dragState.updateScroll(
|
266
266
|
this.element,
|
267
267
|
this.dragItem.row,
|
268
|
-
this.animate
|
268
|
+
this.animate,
|
269
269
|
);
|
270
270
|
});
|
271
271
|
};
|
@@ -325,7 +325,7 @@ class OrderableListController extends Controller {
|
|
325
325
|
if (!this.isDragging) return null;
|
326
326
|
|
327
327
|
return this.tablesOrderableItemOutlets.find(
|
328
|
-
(item) => item.id === this.dragState.targetId
|
328
|
+
(item) => item.id === this.dragState.targetId,
|
329
329
|
);
|
330
330
|
}
|
331
331
|
|
@@ -337,7 +337,7 @@ class OrderableListController extends Controller {
|
|
337
337
|
*/
|
338
338
|
get #currentItems() {
|
339
339
|
return this.tablesOrderableItemOutlets.toSorted(
|
340
|
-
(a, b) => a.comparisonIndex - b.comparisonIndex
|
340
|
+
(a, b) => a.comparisonIndex - b.comparisonIndex,
|
341
341
|
);
|
342
342
|
}
|
343
343
|
|
@@ -349,7 +349,7 @@ class OrderableListController extends Controller {
|
|
349
349
|
*/
|
350
350
|
#targetItem(element) {
|
351
351
|
return this.tablesOrderableItemOutlets.find(
|
352
|
-
(item) => item.element === element
|
352
|
+
(item) => item.element === element,
|
353
353
|
);
|
354
354
|
}
|
355
355
|
|
@@ -455,7 +455,7 @@ class OrderableFormController extends Controller {
|
|
455
455
|
this.element.insertAdjacentHTML(
|
456
456
|
"beforeend",
|
457
457
|
`<input type="hidden" name="${id_name}" value="${id_value}" data-generated>
|
458
|
-
<input type="hidden" name="${index_name}" value="${item.index}" data-generated
|
458
|
+
<input type="hidden" name="${index_name}" value="${item.index}" data-generated>`,
|
459
459
|
);
|
460
460
|
}
|
461
461
|
|
@@ -476,7 +476,7 @@ class OrderableFormController extends Controller {
|
|
476
476
|
|
477
477
|
const Definitions = [
|
478
478
|
{
|
479
|
-
identifier: "tables--turbo--
|
479
|
+
identifier: "tables--turbo--collection",
|
480
480
|
controllerConstructor: TurboCollectionController,
|
481
481
|
},
|
482
482
|
{
|
@@ -198,7 +198,7 @@ class OrderableListController extends Controller {
|
|
198
198
|
|
199
199
|
// reindex all items based on their new positions
|
200
200
|
this.tablesOrderableItemOutlets.forEach((item, index) =>
|
201
|
-
item.updateIndex(index)
|
201
|
+
item.updateIndex(index),
|
202
202
|
);
|
203
203
|
|
204
204
|
// save the changes
|
@@ -250,7 +250,7 @@ class OrderableListController extends Controller {
|
|
250
250
|
this.element,
|
251
251
|
this.dragItem.row,
|
252
252
|
event,
|
253
|
-
this.animate
|
253
|
+
this.animate,
|
254
254
|
);
|
255
255
|
});
|
256
256
|
};
|
@@ -265,7 +265,7 @@ class OrderableListController extends Controller {
|
|
265
265
|
this.dragState.updateScroll(
|
266
266
|
this.element,
|
267
267
|
this.dragItem.row,
|
268
|
-
this.animate
|
268
|
+
this.animate,
|
269
269
|
);
|
270
270
|
});
|
271
271
|
};
|
@@ -325,7 +325,7 @@ class OrderableListController extends Controller {
|
|
325
325
|
if (!this.isDragging) return null;
|
326
326
|
|
327
327
|
return this.tablesOrderableItemOutlets.find(
|
328
|
-
(item) => item.id === this.dragState.targetId
|
328
|
+
(item) => item.id === this.dragState.targetId,
|
329
329
|
);
|
330
330
|
}
|
331
331
|
|
@@ -337,7 +337,7 @@ class OrderableListController extends Controller {
|
|
337
337
|
*/
|
338
338
|
get #currentItems() {
|
339
339
|
return this.tablesOrderableItemOutlets.toSorted(
|
340
|
-
(a, b) => a.comparisonIndex - b.comparisonIndex
|
340
|
+
(a, b) => a.comparisonIndex - b.comparisonIndex,
|
341
341
|
);
|
342
342
|
}
|
343
343
|
|
@@ -349,7 +349,7 @@ class OrderableListController extends Controller {
|
|
349
349
|
*/
|
350
350
|
#targetItem(element) {
|
351
351
|
return this.tablesOrderableItemOutlets.find(
|
352
|
-
(item) => item.element === element
|
352
|
+
(item) => item.element === element,
|
353
353
|
);
|
354
354
|
}
|
355
355
|
|
@@ -455,7 +455,7 @@ class OrderableFormController extends Controller {
|
|
455
455
|
this.element.insertAdjacentHTML(
|
456
456
|
"beforeend",
|
457
457
|
`<input type="hidden" name="${id_name}" value="${id_value}" data-generated>
|
458
|
-
<input type="hidden" name="${index_name}" value="${item.index}" data-generated
|
458
|
+
<input type="hidden" name="${index_name}" value="${item.index}" data-generated>`,
|
459
459
|
);
|
460
460
|
}
|
461
461
|
|
@@ -476,7 +476,7 @@ class OrderableFormController extends Controller {
|
|
476
476
|
|
477
477
|
const Definitions = [
|
478
478
|
{
|
479
|
-
identifier: "tables--turbo--
|
479
|
+
identifier: "tables--turbo--collection",
|
480
480
|
controllerConstructor: TurboCollectionController,
|
481
481
|
},
|
482
482
|
{
|
@@ -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,
|
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)}}const i=[{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]")}}}];export{i as default};
|
2
2
|
//# sourceMappingURL=tables.min.js.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"tables.min.js","sources":["../../../javascript/tables/orderable/list_controller.js","../../../javascript/tables/application.js","../../../javascript/tables/turbo/turbo_collection_controller.js","../../../javascript/tables/orderable/item_controller.js","../../../javascript/tables/orderable/form_controller.js"],"sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\n\nexport default class OrderableListController extends Controller {\n static outlets = [\"tables--orderable--item\", \"tables--orderable--form\"];\n\n //region State transitions\n\n startDragging(dragState) {\n this.dragState = dragState;\n\n document.addEventListener(\"mousemove\", this.mousemove);\n document.addEventListener(\"mouseup\", this.mouseup);\n window.addEventListener(\"scroll\", this.scroll, true);\n\n this.element.style.position = \"relative\";\n }\n\n stopDragging() {\n const dragState = this.dragState;\n delete this.dragState;\n\n document.removeEventListener(\"mousemove\", this.mousemove);\n document.removeEventListener(\"mouseup\", this.mouseup);\n window.removeEventListener(\"scroll\", this.scroll, true);\n\n this.element.removeAttribute(\"style\");\n this.tablesOrderableItemOutlets.forEach((item) => item.reset());\n\n return dragState;\n }\n\n drop() {\n // note: early returns guard against turbo updates that prevent us finding\n // the right item to drop on. In this situation it's better to discard the\n // drop than to drop in the wrong place.\n\n const dragItem = this.dragItem;\n\n if (!dragItem) return;\n\n const newIndex = dragItem.dragIndex;\n const targetItem = this.tablesOrderableItemOutlets[newIndex];\n\n if (!targetItem) return;\n\n // swap the dragged item into the correct position for its current offset\n if (newIndex < dragItem.index) {\n targetItem.row.insertAdjacentElement(\"beforebegin\", dragItem.row);\n } else if (newIndex > dragItem.index) {\n targetItem.row.insertAdjacentElement(\"afterend\", dragItem.row);\n }\n\n // reindex all items based on their new positions\n this.tablesOrderableItemOutlets.forEach((item, index) =>\n item.updateIndex(index)\n );\n\n // save the changes\n this.commitChanges();\n }\n\n commitChanges() {\n // clear any existing inputs to prevent duplicates\n this.tablesOrderableFormOutlet.clear();\n\n // insert any items that have changed position\n this.tablesOrderableItemOutlets.forEach((item) => {\n if (item.hasChanges) this.tablesOrderableFormOutlet.add(item);\n });\n\n this.tablesOrderableFormOutlet.submit();\n }\n\n //endregion\n\n //region Events\n\n mousedown(event) {\n if (this.isDragging) return;\n\n const target = this.#targetItem(event.target);\n\n if (!target) return;\n\n event.preventDefault(); // prevent built-in drag\n\n this.startDragging(new DragState(this.element, event, target.id));\n\n this.dragState.updateCursor(this.element, target.row, event, this.animate);\n }\n\n mousemove = (event) => {\n if (!this.isDragging) return;\n\n event.preventDefault(); // prevent build-in drag\n\n if (this.ticking) return;\n\n this.ticking = true;\n\n window.requestAnimationFrame(() => {\n this.ticking = false;\n this.dragState.updateCursor(\n this.element,\n this.dragItem.row,\n event,\n this.animate\n );\n });\n };\n\n scroll = (event) => {\n if (!this.isDragging || this.ticking) return;\n\n this.ticking = true;\n\n window.requestAnimationFrame(() => {\n this.ticking = false;\n this.dragState.updateScroll(\n this.element,\n this.dragItem.row,\n this.animate\n );\n });\n };\n\n mouseup = (event) => {\n if (!this.isDragging) return;\n\n this.drop();\n this.stopDragging();\n this.tablesOrderableFormOutlets.forEach((form) => delete form.dragState);\n };\n\n tablesOrderableFormOutletConnected(form, element) {\n if (form.dragState) {\n // restore the previous controller's state\n this.startDragging(form.dragState);\n }\n }\n\n tablesOrderableFormOutletDisconnected(form, element) {\n if (this.isDragging) {\n // cache drag state in the form\n form.dragState = this.stopDragging();\n }\n }\n\n //endregion\n\n //region Helpers\n\n /**\n * Updates the position of the drag item with a relative offset. Updates\n * other items relative to the new position of the drag item, as required.\n *\n * @callback {OrderableListController~animate}\n * @param {number} offset\n */\n animate = (offset) => {\n const dragItem = this.dragItem;\n\n // Visually update the dragItem so it follows the cursor\n dragItem.dragUpdate(offset);\n\n // Visually updates the position of all items in the list relative to the\n // dragged item. No actual changes to orderings at this stage.\n this.#currentItems.forEach((item, index) => {\n if (item === dragItem) return;\n item.updateVisually(index);\n });\n };\n\n get isDragging() {\n return !!this.dragState;\n }\n\n get dragItem() {\n if (!this.isDragging) return null;\n\n return this.tablesOrderableItemOutlets.find(\n (item) => item.id === this.dragState.targetId\n );\n }\n\n /**\n * Returns the current items in the list, sorted by their current index.\n * Current uses the drag index if the item is being dragged, if set.\n *\n * @returns {Array[OrderableRowController]}\n */\n get #currentItems() {\n return this.tablesOrderableItemOutlets.toSorted(\n (a, b) => a.comparisonIndex - b.comparisonIndex\n );\n }\n\n /**\n * Returns the item outlet that was clicked on, if any.\n *\n * @param element {HTMLElement} the clicked ordinal cell\n * @returns {OrderableRowController}\n */\n #targetItem(element) {\n return this.tablesOrderableItemOutlets.find(\n (item) => item.element === element\n );\n }\n\n //endregion\n}\n\n/**\n * During drag we want to be able to translate a document-relative coordinate\n * into a coordinate relative to the list element. This state object calculates\n * and stores internal state so that we can translate absolute page coordinates\n * from mouse events into relative offsets for the list items within the list\n * element.\n *\n * We also keep track of the drag target so that if the controller is attached\n * to a new element during the drag we can continue after the turbo update.\n */\nclass DragState {\n /**\n * @param list {HTMLElement} the list controller's element (tbody)\n * @param event {MouseEvent} the initial event\n * @param id {String} the id of the element being dragged\n */\n constructor(list, event, id) {\n // cursor offset is the offset of the cursor relative to the drag item\n this.cursorOffset = event.offsetY;\n\n // initial offset is the offset position of the drag item at drag start\n this.initialPosition = event.target.offsetTop - list.offsetTop;\n\n // id of the item being dragged\n this.targetId = id;\n }\n\n /**\n * Calculates the offset of the drag item relative to its initial position.\n *\n * @param list {HTMLElement} the list controller's element (tbody)\n * @param row {HTMLElement} the row being dragged\n * @param event {MouseEvent} the current event\n * @param callback {OrderableListController~animate} updates the drag item with a relative offset\n */\n updateCursor(list, row, event, callback) {\n // Calculate and store the list offset relative to the viewport\n // This value is cached so we can calculate the outcome of any scroll events\n this.listOffset = list.getBoundingClientRect().top;\n\n // Calculate the position of the cursor relative to the list.\n // Accounts for scroll offsets by using the item's bounding client rect.\n const cursorPosition = event.clientY - this.listOffset;\n\n // intended item position relative to the list, from cursor position\n let itemPosition = cursorPosition - this.cursorOffset;\n\n this.#updateItemPosition(list, row, itemPosition, callback);\n }\n\n /**\n * Animates the item's position as the list scrolls. Requires a previous call\n * to set the scroll offset.\n *\n * @param list {HTMLElement} the list controller's element (tbody)\n * @param row {HTMLElement} the row being dragged\n * @param callback {OrderableListController~animate} updates the drag item with a relative offset\n */\n updateScroll(list, row, callback) {\n const previousScrollOffset = this.listOffset;\n\n // Calculate and store the list offset relative to the viewport\n // This value is cached so we can calculate the outcome of any scroll events\n this.listOffset = list.getBoundingClientRect().top;\n\n // Calculate the change in scroll offset since the last update\n const scrollDelta = previousScrollOffset - this.listOffset;\n\n // intended item position relative to the list, from cursor position\n const position = this.position + scrollDelta;\n\n this.#updateItemPosition(list, row, position, callback);\n }\n\n #updateItemPosition(list, row, position, callback) {\n // ensure itemPosition is within the bounds of the list (tbody)\n position = Math.max(position, 0);\n position = Math.min(position, list.offsetHeight - row.offsetHeight);\n\n // cache the item's position relative to the list for use in scroll events\n this.position = position;\n\n // Item has position: relative, so we want to calculate the amount to move\n // the item relative to it's DOM position to represent how much it has been\n // dragged by.\n const offset = position - this.initialPosition;\n\n // Convert itemPosition from offset relative to list to offset relative to\n // its position within the DOM (if it hadn't moved).\n callback(offset);\n }\n}\n","import TurboCollectionController from \"./turbo/turbo_collection_controller\";\nimport ItemController from \"./orderable/item_controller\";\nimport ListController from \"./orderable/list_controller\";\nimport FormController from \"./orderable/form_controller\";\n\nconst Definitions = [\n {\n identifier: \"tables--turbo--turbo-collection\",\n controllerConstructor: TurboCollectionController,\n },\n {\n identifier: \"tables--orderable--item\",\n controllerConstructor: ItemController,\n },\n {\n identifier: \"tables--orderable--list\",\n controllerConstructor: ListController,\n },\n {\n identifier: \"tables--orderable--form\",\n controllerConstructor: FormController,\n },\n];\n\nexport { Definitions as default };\n","import { Controller } from \"@hotwired/stimulus\";\nimport { Turbo } from \"@hotwired/turbo-rails\";\n\nexport default class TurboCollectionController extends Controller {\n static values = {\n query: String,\n sort: String,\n };\n\n queryValueChanged(query) {\n Turbo.navigator.history.replace(this.#url(query));\n }\n\n sortValueChanged(sort) {\n document.querySelectorAll(this.#sortSelector).forEach((input) => {\n if (input) input.value = sort;\n });\n }\n\n get #sortSelector() {\n return \"input[name='sort']\";\n }\n\n #url(query) {\n const frame = this.element.closest(\"turbo-frame\");\n let url;\n\n if (frame) {\n url = new URL(frame.baseURI);\n } else {\n url = new URL(window.location.href);\n }\n\n url.search = query;\n\n return url;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class OrderableRowController extends Controller {\n static values = {\n params: Object,\n };\n\n connect() {\n // index from server may be inconsistent with the visual ordering,\n // especially if this is a new node. Use positional indexes instead,\n // as these are the values we will send on save.\n this.index = domIndex(this.row);\n }\n\n paramsValueChanged(params) {\n this.id = params.id_value;\n }\n\n dragUpdate(offset) {\n this.dragOffset = offset;\n this.row.style.position = \"relative\";\n this.row.style.top = offset + \"px\";\n this.row.style.zIndex = \"1\";\n this.row.toggleAttribute(\"dragging\", true);\n }\n\n /**\n * Called on items that are not the dragged item during drag. Updates the\n * visual position of the item relative to the dragged item.\n *\n * @param index {number} intended index of the item during drag\n */\n updateVisually(index) {\n this.row.style.position = \"relative\";\n this.row.style.top = `${\n this.row.offsetHeight * (index - this.dragIndex)\n }px`;\n }\n\n /**\n * Set the index value of the item. This is called on all items after a drop\n * event. If the index is different to the params index then this item has\n * changed.\n *\n * @param index {number} the new index value\n */\n updateIndex(index) {\n this.index = index;\n }\n\n /**\n * Restore any visual changes made during drag and remove the drag state.\n */\n reset() {\n delete this.dragOffset;\n this.row.removeAttribute(\"style\");\n this.row.removeAttribute(\"dragging\");\n }\n\n /**\n * @returns {boolean} true when the item has a change to its index value\n */\n get hasChanges() {\n return this.paramsValue.index_value !== this.index;\n }\n\n /**\n * Calculate the relative index of the item during drag. This is used to\n * sort items during drag as it takes into account any uncommitted changes\n * to index caused by the drag offset.\n *\n * @returns {number} index for the purposes of drag and drop ordering\n */\n get dragIndex() {\n if (this.dragOffset && this.dragOffset !== 0) {\n return this.index + Math.round(this.dragOffset / this.row.offsetHeight);\n } else {\n return this.index;\n }\n }\n\n /**\n * Index value for use in comparisons during drag. This is used to determine\n * whether the dragged item is above or below another item. If this item is\n * being dragged then we offset the index by 0.5 to ensure that it jumps up\n * or down when it reaches the midpoint of the item above or below it.\n *\n * @returns {number}\n */\n get comparisonIndex() {\n if (this.dragOffset) {\n return this.dragIndex + (this.dragOffset > 0 ? 0.5 : -0.5);\n } else {\n return this.index;\n }\n }\n\n /**\n * The containing row element.\n *\n * @returns {HTMLElement}\n */\n get row() {\n return this.element.parentElement;\n }\n}\n\nfunction domIndex(element) {\n return Array.from(element.parentElement.children).indexOf(element);\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class OrderableFormController extends Controller {\n add(item) {\n const { id_name, id_value, index_name } = item.paramsValue;\n this.element.insertAdjacentHTML(\n \"beforeend\",\n `<input type=\"hidden\" name=\"${id_name}\" value=\"${id_value}\" data-generated>\n <input type=\"hidden\" name=\"${index_name}\" value=\"${item.index}\" data-generated>`\n );\n }\n\n submit() {\n if (this.inputs.length === 0) return;\n\n this.element.requestSubmit();\n }\n\n clear() {\n this.inputs.forEach((input) => input.remove());\n }\n\n get inputs() {\n return this.element.querySelectorAll(\"input[data-generated]\");\n }\n}\n"],"names":["DragState","constructor","list","event","id","this","cursorOffset","offsetY","initialPosition","target","offsetTop","targetId","updateCursor","row","callback","listOffset","getBoundingClientRect","top","itemPosition","clientY","updateItemPosition","updateScroll","previousScrollOffset","scrollDelta","position","Math","max","min","offsetHeight","Definitions","identifier","controllerConstructor","Controller","static","query","String","sort","queryValueChanged","Turbo","navigator","history","replace","url","sortValueChanged","document","querySelectorAll","sortSelector","forEach","input","value","frame","element","closest","URL","baseURI","window","location","href","search","params","Object","connect","index","Array","from","parentElement","children","indexOf","paramsValueChanged","id_value","dragUpdate","offset","dragOffset","style","zIndex","toggleAttribute","updateVisually","dragIndex","updateIndex","reset","removeAttribute","hasChanges","paramsValue","index_value","round","comparisonIndex","startDragging","dragState","addEventListener","mousemove","mouseup","scroll","stopDragging","removeEventListener","tablesOrderableItemOutlets","item","drop","dragItem","newIndex","targetItem","insertAdjacentElement","commitChanges","tablesOrderableFormOutlet","clear","add","submit","mousedown","isDragging","preventDefault","animate","ticking","requestAnimationFrame","tablesOrderableFormOutlets","form","tablesOrderableFormOutletConnected","tablesOrderableFormOutletDisconnected","currentItems","find","toSorted","a","b","id_name","index_name","insertAdjacentHTML","inputs","length","requestSubmit","remove"],"mappings":"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"}
|
1
|
+
{"version":3,"file":"tables.min.js","sources":["../../../javascript/tables/orderable/list_controller.js","../../../javascript/tables/application.js","../../../javascript/tables/turbo/collection_controller.js","../../../javascript/tables/orderable/item_controller.js","../../../javascript/tables/orderable/form_controller.js"],"sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\n\nexport default class OrderableListController extends Controller {\n static outlets = [\"tables--orderable--item\", \"tables--orderable--form\"];\n\n //region State transitions\n\n startDragging(dragState) {\n this.dragState = dragState;\n\n document.addEventListener(\"mousemove\", this.mousemove);\n document.addEventListener(\"mouseup\", this.mouseup);\n window.addEventListener(\"scroll\", this.scroll, true);\n\n this.element.style.position = \"relative\";\n }\n\n stopDragging() {\n const dragState = this.dragState;\n delete this.dragState;\n\n document.removeEventListener(\"mousemove\", this.mousemove);\n document.removeEventListener(\"mouseup\", this.mouseup);\n window.removeEventListener(\"scroll\", this.scroll, true);\n\n this.element.removeAttribute(\"style\");\n this.tablesOrderableItemOutlets.forEach((item) => item.reset());\n\n return dragState;\n }\n\n drop() {\n // note: early returns guard against turbo updates that prevent us finding\n // the right item to drop on. In this situation it's better to discard the\n // drop than to drop in the wrong place.\n\n const dragItem = this.dragItem;\n\n if (!dragItem) return;\n\n const newIndex = dragItem.dragIndex;\n const targetItem = this.tablesOrderableItemOutlets[newIndex];\n\n if (!targetItem) return;\n\n // swap the dragged item into the correct position for its current offset\n if (newIndex < dragItem.index) {\n targetItem.row.insertAdjacentElement(\"beforebegin\", dragItem.row);\n } else if (newIndex > dragItem.index) {\n targetItem.row.insertAdjacentElement(\"afterend\", dragItem.row);\n }\n\n // reindex all items based on their new positions\n this.tablesOrderableItemOutlets.forEach((item, index) =>\n item.updateIndex(index),\n );\n\n // save the changes\n this.commitChanges();\n }\n\n commitChanges() {\n // clear any existing inputs to prevent duplicates\n this.tablesOrderableFormOutlet.clear();\n\n // insert any items that have changed position\n this.tablesOrderableItemOutlets.forEach((item) => {\n if (item.hasChanges) this.tablesOrderableFormOutlet.add(item);\n });\n\n this.tablesOrderableFormOutlet.submit();\n }\n\n //endregion\n\n //region Events\n\n mousedown(event) {\n if (this.isDragging) return;\n\n const target = this.#targetItem(event.target);\n\n if (!target) return;\n\n event.preventDefault(); // prevent built-in drag\n\n this.startDragging(new DragState(this.element, event, target.id));\n\n this.dragState.updateCursor(this.element, target.row, event, this.animate);\n }\n\n mousemove = (event) => {\n if (!this.isDragging) return;\n\n event.preventDefault(); // prevent build-in drag\n\n if (this.ticking) return;\n\n this.ticking = true;\n\n window.requestAnimationFrame(() => {\n this.ticking = false;\n this.dragState.updateCursor(\n this.element,\n this.dragItem.row,\n event,\n this.animate,\n );\n });\n };\n\n scroll = (event) => {\n if (!this.isDragging || this.ticking) return;\n\n this.ticking = true;\n\n window.requestAnimationFrame(() => {\n this.ticking = false;\n this.dragState.updateScroll(\n this.element,\n this.dragItem.row,\n this.animate,\n );\n });\n };\n\n mouseup = (event) => {\n if (!this.isDragging) return;\n\n this.drop();\n this.stopDragging();\n this.tablesOrderableFormOutlets.forEach((form) => delete form.dragState);\n };\n\n tablesOrderableFormOutletConnected(form, element) {\n if (form.dragState) {\n // restore the previous controller's state\n this.startDragging(form.dragState);\n }\n }\n\n tablesOrderableFormOutletDisconnected(form, element) {\n if (this.isDragging) {\n // cache drag state in the form\n form.dragState = this.stopDragging();\n }\n }\n\n //endregion\n\n //region Helpers\n\n /**\n * Updates the position of the drag item with a relative offset. Updates\n * other items relative to the new position of the drag item, as required.\n *\n * @callback {OrderableListController~animate}\n * @param {number} offset\n */\n animate = (offset) => {\n const dragItem = this.dragItem;\n\n // Visually update the dragItem so it follows the cursor\n dragItem.dragUpdate(offset);\n\n // Visually updates the position of all items in the list relative to the\n // dragged item. No actual changes to orderings at this stage.\n this.#currentItems.forEach((item, index) => {\n if (item === dragItem) return;\n item.updateVisually(index);\n });\n };\n\n get isDragging() {\n return !!this.dragState;\n }\n\n get dragItem() {\n if (!this.isDragging) return null;\n\n return this.tablesOrderableItemOutlets.find(\n (item) => item.id === this.dragState.targetId,\n );\n }\n\n /**\n * Returns the current items in the list, sorted by their current index.\n * Current uses the drag index if the item is being dragged, if set.\n *\n * @returns {Array[OrderableRowController]}\n */\n get #currentItems() {\n return this.tablesOrderableItemOutlets.toSorted(\n (a, b) => a.comparisonIndex - b.comparisonIndex,\n );\n }\n\n /**\n * Returns the item outlet that was clicked on, if any.\n *\n * @param element {HTMLElement} the clicked ordinal cell\n * @returns {OrderableRowController}\n */\n #targetItem(element) {\n return this.tablesOrderableItemOutlets.find(\n (item) => item.element === element,\n );\n }\n\n //endregion\n}\n\n/**\n * During drag we want to be able to translate a document-relative coordinate\n * into a coordinate relative to the list element. This state object calculates\n * and stores internal state so that we can translate absolute page coordinates\n * from mouse events into relative offsets for the list items within the list\n * element.\n *\n * We also keep track of the drag target so that if the controller is attached\n * to a new element during the drag we can continue after the turbo update.\n */\nclass DragState {\n /**\n * @param list {HTMLElement} the list controller's element (tbody)\n * @param event {MouseEvent} the initial event\n * @param id {String} the id of the element being dragged\n */\n constructor(list, event, id) {\n // cursor offset is the offset of the cursor relative to the drag item\n this.cursorOffset = event.offsetY;\n\n // initial offset is the offset position of the drag item at drag start\n this.initialPosition = event.target.offsetTop - list.offsetTop;\n\n // id of the item being dragged\n this.targetId = id;\n }\n\n /**\n * Calculates the offset of the drag item relative to its initial position.\n *\n * @param list {HTMLElement} the list controller's element (tbody)\n * @param row {HTMLElement} the row being dragged\n * @param event {MouseEvent} the current event\n * @param callback {OrderableListController~animate} updates the drag item with a relative offset\n */\n updateCursor(list, row, event, callback) {\n // Calculate and store the list offset relative to the viewport\n // This value is cached so we can calculate the outcome of any scroll events\n this.listOffset = list.getBoundingClientRect().top;\n\n // Calculate the position of the cursor relative to the list.\n // Accounts for scroll offsets by using the item's bounding client rect.\n const cursorPosition = event.clientY - this.listOffset;\n\n // intended item position relative to the list, from cursor position\n let itemPosition = cursorPosition - this.cursorOffset;\n\n this.#updateItemPosition(list, row, itemPosition, callback);\n }\n\n /**\n * Animates the item's position as the list scrolls. Requires a previous call\n * to set the scroll offset.\n *\n * @param list {HTMLElement} the list controller's element (tbody)\n * @param row {HTMLElement} the row being dragged\n * @param callback {OrderableListController~animate} updates the drag item with a relative offset\n */\n updateScroll(list, row, callback) {\n const previousScrollOffset = this.listOffset;\n\n // Calculate and store the list offset relative to the viewport\n // This value is cached so we can calculate the outcome of any scroll events\n this.listOffset = list.getBoundingClientRect().top;\n\n // Calculate the change in scroll offset since the last update\n const scrollDelta = previousScrollOffset - this.listOffset;\n\n // intended item position relative to the list, from cursor position\n const position = this.position + scrollDelta;\n\n this.#updateItemPosition(list, row, position, callback);\n }\n\n #updateItemPosition(list, row, position, callback) {\n // ensure itemPosition is within the bounds of the list (tbody)\n position = Math.max(position, 0);\n position = Math.min(position, list.offsetHeight - row.offsetHeight);\n\n // cache the item's position relative to the list for use in scroll events\n this.position = position;\n\n // Item has position: relative, so we want to calculate the amount to move\n // the item relative to it's DOM position to represent how much it has been\n // dragged by.\n const offset = position - this.initialPosition;\n\n // Convert itemPosition from offset relative to list to offset relative to\n // its position within the DOM (if it hadn't moved).\n callback(offset);\n }\n}\n","import TurboCollectionController from \"./turbo/collection_controller\";\nimport ItemController from \"./orderable/item_controller\";\nimport ListController from \"./orderable/list_controller\";\nimport FormController from \"./orderable/form_controller\";\n\nconst Definitions = [\n {\n identifier: \"tables--turbo--collection\",\n controllerConstructor: TurboCollectionController,\n },\n {\n identifier: \"tables--orderable--item\",\n controllerConstructor: ItemController,\n },\n {\n identifier: \"tables--orderable--list\",\n controllerConstructor: ListController,\n },\n {\n identifier: \"tables--orderable--form\",\n controllerConstructor: FormController,\n },\n];\n\nexport { Definitions as default };\n","import { Controller } from \"@hotwired/stimulus\";\nimport { Turbo } from \"@hotwired/turbo-rails\";\n\nexport default class TurboCollectionController extends Controller {\n static values = {\n query: String,\n sort: String,\n };\n\n queryValueChanged(query) {\n Turbo.navigator.history.replace(this.#url(query));\n }\n\n sortValueChanged(sort) {\n document.querySelectorAll(this.#sortSelector).forEach((input) => {\n if (input) input.value = sort;\n });\n }\n\n get #sortSelector() {\n return \"input[name='sort']\";\n }\n\n #url(query) {\n const frame = this.element.closest(\"turbo-frame\");\n let url;\n\n if (frame) {\n url = new URL(frame.baseURI);\n } else {\n url = new URL(window.location.href);\n }\n\n url.search = query;\n\n return url;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class OrderableRowController extends Controller {\n static values = {\n params: Object,\n };\n\n connect() {\n // index from server may be inconsistent with the visual ordering,\n // especially if this is a new node. Use positional indexes instead,\n // as these are the values we will send on save.\n this.index = domIndex(this.row);\n }\n\n paramsValueChanged(params) {\n this.id = params.id_value;\n }\n\n dragUpdate(offset) {\n this.dragOffset = offset;\n this.row.style.position = \"relative\";\n this.row.style.top = offset + \"px\";\n this.row.style.zIndex = \"1\";\n this.row.toggleAttribute(\"dragging\", true);\n }\n\n /**\n * Called on items that are not the dragged item during drag. Updates the\n * visual position of the item relative to the dragged item.\n *\n * @param index {number} intended index of the item during drag\n */\n updateVisually(index) {\n this.row.style.position = \"relative\";\n this.row.style.top = `${\n this.row.offsetHeight * (index - this.dragIndex)\n }px`;\n }\n\n /**\n * Set the index value of the item. This is called on all items after a drop\n * event. If the index is different to the params index then this item has\n * changed.\n *\n * @param index {number} the new index value\n */\n updateIndex(index) {\n this.index = index;\n }\n\n /**\n * Restore any visual changes made during drag and remove the drag state.\n */\n reset() {\n delete this.dragOffset;\n this.row.removeAttribute(\"style\");\n this.row.removeAttribute(\"dragging\");\n }\n\n /**\n * @returns {boolean} true when the item has a change to its index value\n */\n get hasChanges() {\n return this.paramsValue.index_value !== this.index;\n }\n\n /**\n * Calculate the relative index of the item during drag. This is used to\n * sort items during drag as it takes into account any uncommitted changes\n * to index caused by the drag offset.\n *\n * @returns {number} index for the purposes of drag and drop ordering\n */\n get dragIndex() {\n if (this.dragOffset && this.dragOffset !== 0) {\n return this.index + Math.round(this.dragOffset / this.row.offsetHeight);\n } else {\n return this.index;\n }\n }\n\n /**\n * Index value for use in comparisons during drag. This is used to determine\n * whether the dragged item is above or below another item. If this item is\n * being dragged then we offset the index by 0.5 to ensure that it jumps up\n * or down when it reaches the midpoint of the item above or below it.\n *\n * @returns {number}\n */\n get comparisonIndex() {\n if (this.dragOffset) {\n return this.dragIndex + (this.dragOffset > 0 ? 0.5 : -0.5);\n } else {\n return this.index;\n }\n }\n\n /**\n * The containing row element.\n *\n * @returns {HTMLElement}\n */\n get row() {\n return this.element.parentElement;\n }\n}\n\nfunction domIndex(element) {\n return Array.from(element.parentElement.children).indexOf(element);\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class OrderableFormController extends Controller {\n add(item) {\n const { id_name, id_value, index_name } = item.paramsValue;\n this.element.insertAdjacentHTML(\n \"beforeend\",\n `<input type=\"hidden\" name=\"${id_name}\" value=\"${id_value}\" data-generated>\n <input type=\"hidden\" name=\"${index_name}\" value=\"${item.index}\" data-generated>`,\n );\n }\n\n submit() {\n if (this.inputs.length === 0) return;\n\n this.element.requestSubmit();\n }\n\n clear() {\n this.inputs.forEach((input) => input.remove());\n }\n\n get inputs() {\n return this.element.querySelectorAll(\"input[data-generated]\");\n }\n}\n"],"names":["DragState","constructor","list","event","id","this","cursorOffset","offsetY","initialPosition","target","offsetTop","targetId","updateCursor","row","callback","listOffset","getBoundingClientRect","top","itemPosition","clientY","updateItemPosition","updateScroll","previousScrollOffset","scrollDelta","position","Math","max","min","offsetHeight","Definitions","identifier","controllerConstructor","Controller","static","query","String","sort","queryValueChanged","Turbo","navigator","history","replace","url","sortValueChanged","document","querySelectorAll","sortSelector","forEach","input","value","frame","element","closest","URL","baseURI","window","location","href","search","params","Object","connect","index","Array","from","parentElement","children","indexOf","paramsValueChanged","id_value","dragUpdate","offset","dragOffset","style","zIndex","toggleAttribute","updateVisually","dragIndex","updateIndex","reset","removeAttribute","hasChanges","paramsValue","index_value","round","comparisonIndex","startDragging","dragState","addEventListener","mousemove","mouseup","scroll","stopDragging","removeEventListener","tablesOrderableItemOutlets","item","drop","dragItem","newIndex","targetItem","insertAdjacentElement","commitChanges","tablesOrderableFormOutlet","clear","add","submit","mousedown","isDragging","preventDefault","animate","ticking","requestAnimationFrame","tablesOrderableFormOutlets","form","tablesOrderableFormOutletConnected","tablesOrderableFormOutletDisconnected","currentItems","find","toSorted","a","b","id_name","index_name","insertAdjacentHTML","inputs","length","requestSubmit","remove"],"mappings":"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"}
|
@@ -24,9 +24,9 @@ module Katalyst
|
|
24
24
|
# other tables.
|
25
25
|
def config_component(name, component_name: "#{name}_component", default: nil) # rubocop:disable Metrics/MethodLength
|
26
26
|
config_accessor(name)
|
27
|
-
config.public_send("#{name}=", default)
|
27
|
+
config.public_send(:"#{name}=", default)
|
28
28
|
define_method(component_name) do
|
29
|
-
return instance_variable_get("@#{component_name}") if instance_variable_defined?("@#{component_name}")
|
29
|
+
return instance_variable_get(:"@#{component_name}") if instance_variable_defined?(:"@#{component_name}")
|
30
30
|
|
31
31
|
klass = config.public_send(name)
|
32
32
|
component = klass ? self.class.const_get(klass) : nil
|
@@ -37,7 +37,7 @@ module Katalyst
|
|
37
37
|
component.extend(HiddenSubcomponent)
|
38
38
|
end
|
39
39
|
|
40
|
-
instance_variable_set("@#{component_name}", component) if component
|
40
|
+
instance_variable_set(:"@#{component_name}", component) if component
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
@@ -24,9 +24,23 @@ module Katalyst
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def row_partial(row, record = nil)
|
27
|
-
|
28
|
-
as
|
29
|
-
render(partial: partial, variants: [:row], formats: [:html], locals: { as => record, row: row })
|
27
|
+
@partial ||= partial_path
|
28
|
+
@as ||= template_name
|
29
|
+
render(partial: @partial, variants: [:row], formats: [:html], locals: { @as => record, row: row })
|
30
|
+
end
|
31
|
+
|
32
|
+
def partial_path
|
33
|
+
# Collection::Base overwrites param_key for form_with compatibility
|
34
|
+
items.model_name.param_key.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def template_name
|
38
|
+
# Collection::Base overwrites param_key for form_with compatibility
|
39
|
+
items.model_name.param_key.to_sym
|
40
|
+
end
|
41
|
+
|
42
|
+
def items
|
43
|
+
collection.respond_to?(:items) ? collection.items : collection
|
30
44
|
end
|
31
45
|
end
|
32
46
|
end
|
@@ -40,7 +40,8 @@ module Katalyst
|
|
40
40
|
**html_attributes)
|
41
41
|
@collection = collection
|
42
42
|
|
43
|
-
# sorting: instance of Katalyst::Tables::
|
43
|
+
# sorting: instance of Katalyst::Tables::Collection::SortForm.
|
44
|
+
# If not provided will be inferred from the collection.
|
44
45
|
@sorting = sorting || html_attributes.delete(:sort) # backwards compatibility with old `sort` option
|
45
46
|
|
46
47
|
# header: true means render the header row, header: false means no header row, if a hash, passes as options
|
@@ -26,9 +26,9 @@ module Katalyst
|
|
26
26
|
def default_html_attributes
|
27
27
|
{
|
28
28
|
data: {
|
29
|
-
controller:
|
30
|
-
|
31
|
-
|
29
|
+
controller: "tables--turbo--collection",
|
30
|
+
tables__turbo__collection_query_value: current_query,
|
31
|
+
tables__turbo__collection_sort_value: collection.sort,
|
32
32
|
},
|
33
33
|
}
|
34
34
|
end
|
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support"
|
4
|
-
require "active_support/concern"
|
5
|
-
|
6
|
-
require_relative "backend/sort_form"
|
7
|
-
|
8
3
|
module Katalyst
|
9
4
|
module Tables
|
10
5
|
# Utilities for controllers that are generating collections for visualisation
|
@@ -14,6 +9,10 @@ module Katalyst
|
|
14
9
|
module Backend
|
15
10
|
extend ActiveSupport::Concern
|
16
11
|
|
12
|
+
# @deprecated backwards compatibility
|
13
|
+
class SortForm < Katalyst::Tables::Collection::SortForm
|
14
|
+
end
|
15
|
+
|
17
16
|
# Sort the given collection by params[:sort], which is set when a user
|
18
17
|
# interacts with a column header in a frontend table view.
|
19
18
|
#
|
@@ -1,11 +1,11 @@
|
|
1
|
-
import TurboCollectionController from "./turbo/
|
1
|
+
import TurboCollectionController from "./turbo/collection_controller";
|
2
2
|
import ItemController from "./orderable/item_controller";
|
3
3
|
import ListController from "./orderable/list_controller";
|
4
4
|
import FormController from "./orderable/form_controller";
|
5
5
|
|
6
6
|
const Definitions = [
|
7
7
|
{
|
8
|
-
identifier: "tables--turbo--
|
8
|
+
identifier: "tables--turbo--collection",
|
9
9
|
controllerConstructor: TurboCollectionController,
|
10
10
|
},
|
11
11
|
{
|
@@ -6,7 +6,7 @@ export default class OrderableFormController extends Controller {
|
|
6
6
|
this.element.insertAdjacentHTML(
|
7
7
|
"beforeend",
|
8
8
|
`<input type="hidden" name="${id_name}" value="${id_value}" data-generated>
|
9
|
-
<input type="hidden" name="${index_name}" value="${item.index}" data-generated
|
9
|
+
<input type="hidden" name="${index_name}" value="${item.index}" data-generated>`,
|
10
10
|
);
|
11
11
|
}
|
12
12
|
|
@@ -52,7 +52,7 @@ export default class OrderableListController extends Controller {
|
|
52
52
|
|
53
53
|
// reindex all items based on their new positions
|
54
54
|
this.tablesOrderableItemOutlets.forEach((item, index) =>
|
55
|
-
item.updateIndex(index)
|
55
|
+
item.updateIndex(index),
|
56
56
|
);
|
57
57
|
|
58
58
|
// save the changes
|
@@ -104,7 +104,7 @@ export default class OrderableListController extends Controller {
|
|
104
104
|
this.element,
|
105
105
|
this.dragItem.row,
|
106
106
|
event,
|
107
|
-
this.animate
|
107
|
+
this.animate,
|
108
108
|
);
|
109
109
|
});
|
110
110
|
};
|
@@ -119,7 +119,7 @@ export default class OrderableListController extends Controller {
|
|
119
119
|
this.dragState.updateScroll(
|
120
120
|
this.element,
|
121
121
|
this.dragItem.row,
|
122
|
-
this.animate
|
122
|
+
this.animate,
|
123
123
|
);
|
124
124
|
});
|
125
125
|
};
|
@@ -179,7 +179,7 @@ export default class OrderableListController extends Controller {
|
|
179
179
|
if (!this.isDragging) return null;
|
180
180
|
|
181
181
|
return this.tablesOrderableItemOutlets.find(
|
182
|
-
(item) => item.id === this.dragState.targetId
|
182
|
+
(item) => item.id === this.dragState.targetId,
|
183
183
|
);
|
184
184
|
}
|
185
185
|
|
@@ -191,7 +191,7 @@ export default class OrderableListController extends Controller {
|
|
191
191
|
*/
|
192
192
|
get #currentItems() {
|
193
193
|
return this.tablesOrderableItemOutlets.toSorted(
|
194
|
-
(a, b) => a.comparisonIndex - b.comparisonIndex
|
194
|
+
(a, b) => a.comparisonIndex - b.comparisonIndex,
|
195
195
|
);
|
196
196
|
}
|
197
197
|
|
@@ -203,7 +203,7 @@ export default class OrderableListController extends Controller {
|
|
203
203
|
*/
|
204
204
|
#targetItem(element) {
|
205
205
|
return this.tablesOrderableItemOutlets.find(
|
206
|
-
(item) => item.element === element
|
206
|
+
(item) => item.element === element,
|
207
207
|
);
|
208
208
|
}
|
209
209
|
|
@@ -11,6 +11,7 @@ module Katalyst
|
|
11
11
|
include ActiveModel::Dirty
|
12
12
|
include ActiveSupport::Configurable
|
13
13
|
|
14
|
+
include HasParams
|
14
15
|
include Reducers
|
15
16
|
|
16
17
|
class_methods do
|
@@ -29,8 +30,7 @@ module Katalyst
|
|
29
30
|
included do
|
30
31
|
attr_accessor :items
|
31
32
|
|
32
|
-
delegate :
|
33
|
-
delegate :model_name, to: :model, allow_nil: true
|
33
|
+
delegate :each, :count, :empty?, to: :items, allow_nil: true
|
34
34
|
end
|
35
35
|
|
36
36
|
def initialize(**options)
|
@@ -39,18 +39,6 @@ module Katalyst
|
|
39
39
|
clear_changes_information
|
40
40
|
end
|
41
41
|
|
42
|
-
def filter
|
43
|
-
# no-op by default
|
44
|
-
end
|
45
|
-
|
46
|
-
def filtered?
|
47
|
-
self.class.new.filters != filters
|
48
|
-
end
|
49
|
-
|
50
|
-
def filters
|
51
|
-
attributes.except("page", "sort")
|
52
|
-
end
|
53
|
-
|
54
42
|
def apply(items)
|
55
43
|
@items = items
|
56
44
|
reducers.build do |_|
|
@@ -59,21 +47,6 @@ module Katalyst
|
|
59
47
|
end.call(self)
|
60
48
|
self
|
61
49
|
end
|
62
|
-
|
63
|
-
def with_params(params)
|
64
|
-
self.attributes = params.permit(self.class.permitted_params)
|
65
|
-
|
66
|
-
self
|
67
|
-
end
|
68
|
-
|
69
|
-
# Returns a hash of the current attributes that have changed from defaults.
|
70
|
-
def to_params
|
71
|
-
attributes.slice(*changed)
|
72
|
-
end
|
73
|
-
|
74
|
-
def inspect
|
75
|
-
"#<#{self.class.name} @attributes=#{attributes.inspect} @model=\"#{model}\" @count=#{items&.count}>"
|
76
|
-
end
|
77
50
|
end
|
78
51
|
end
|
79
52
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module Collection
|
6
|
+
using HasParams
|
7
|
+
|
8
|
+
module Filtering # :nodoc:
|
9
|
+
def filter
|
10
|
+
# no-op by default
|
11
|
+
end
|
12
|
+
|
13
|
+
def filtered?
|
14
|
+
filters.any?
|
15
|
+
end
|
16
|
+
|
17
|
+
def filters
|
18
|
+
changed_attributes.except("sort", "page")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module Collection
|
6
|
+
module HasParams # :nodoc:
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
refine Object do
|
10
|
+
def to_params
|
11
|
+
as_json
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
refine ActiveModel::AttributeSet do
|
16
|
+
def to_params
|
17
|
+
to_h.transform_values(&:to_params).as_json
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
refine ActiveModel::Attributes do
|
22
|
+
def to_params
|
23
|
+
if respond_to?(:changed)
|
24
|
+
@attributes.to_params.slice(*changed)
|
25
|
+
else
|
26
|
+
@attributes.to_params
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
using HasParams
|
32
|
+
|
33
|
+
# Returns a hash of the current attributes that have changed from defaults.
|
34
|
+
# This uses Refinements internally so it needs to be exposed publicly with this super call.
|
35
|
+
# rubocop:disable Lint/UselessMethodDefinition
|
36
|
+
def to_params
|
37
|
+
super
|
38
|
+
end
|
39
|
+
# rubocop:enable Lint/UselessMethodDefinition
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -22,7 +22,7 @@ module Katalyst
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def initialize(sorting: config.sorting, **options)
|
25
|
-
@sorting =
|
25
|
+
@sorting = SortForm.parse(sorting) if sorting
|
26
26
|
|
27
27
|
super(sort: sorting, **options) # set default sort based on config
|
28
28
|
end
|
@@ -31,7 +31,7 @@ module Katalyst
|
|
31
31
|
return unless @sorting
|
32
32
|
|
33
33
|
# update internal proxy
|
34
|
-
@sorting =
|
34
|
+
@sorting = SortForm.parse(value, default: attribute_was(:sort))
|
35
35
|
|
36
36
|
# update attribute based on normalized value
|
37
37
|
super(@sorting.to_param)
|
@@ -3,7 +3,9 @@
|
|
3
3
|
module Katalyst
|
4
4
|
module Tables
|
5
5
|
module Collection
|
6
|
-
# Entry point for creating a collection for use with table components
|
6
|
+
# Entry point for creating a collection for use with table components
|
7
|
+
# where filter params are flat, e.g. ?search=query
|
8
|
+
#
|
7
9
|
# This class is intended to be subclassed, i.e.:
|
8
10
|
#
|
9
11
|
# class ApplicationController < ActionController::Base
|
@@ -21,11 +23,35 @@ module Katalyst
|
|
21
23
|
# ````
|
22
24
|
class Base
|
23
25
|
include Core
|
26
|
+
include Filtering
|
24
27
|
include Pagination
|
25
28
|
include Sorting
|
26
29
|
|
27
30
|
use(Sorting::Sort)
|
28
31
|
use(Pagination::Paginate)
|
32
|
+
|
33
|
+
def self.with_params(params)
|
34
|
+
new.with_params(params)
|
35
|
+
end
|
36
|
+
|
37
|
+
def model_name
|
38
|
+
@model_name ||= items.model_name.dup.tap do |name|
|
39
|
+
name.param_key = ""
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def with_params(params)
|
44
|
+
# test support
|
45
|
+
params = ActionController::Parameters.new(params) unless params.is_a?(ActionController::Parameters)
|
46
|
+
|
47
|
+
self.attributes = params.permit(self.class.permitted_params)
|
48
|
+
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def inspect
|
53
|
+
"#<#{self.class.name} @attributes=#{attributes.inspect} @model_name=\"#{model_name}\" @count=#{items&.count}>"
|
54
|
+
end
|
29
55
|
end
|
30
56
|
end
|
31
57
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module Collection
|
6
|
+
# Entry point for creating a collection for use with table components
|
7
|
+
# where filter params are nested, e.g. ?filters[search]=query
|
8
|
+
#
|
9
|
+
# This class is intended to be subclassed, i.e.:
|
10
|
+
#
|
11
|
+
# class ApplicationController < ActionController::Base
|
12
|
+
# class Collection < Katalyst::Tables::Collection::Base
|
13
|
+
# ...
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# In the context of a controller action, construct a collection, apply it
|
18
|
+
# to a model, then pass the result to the view component:
|
19
|
+
# ```
|
20
|
+
# collection = Collection.new.with_params(params).apply(People.all)
|
21
|
+
# table = Katalyst::TableComponent.new(collection: collection)
|
22
|
+
# render table
|
23
|
+
# ````
|
24
|
+
class Filter
|
25
|
+
include Core
|
26
|
+
include Filtering
|
27
|
+
include Pagination
|
28
|
+
include Sorting
|
29
|
+
|
30
|
+
use(Sorting::Sort)
|
31
|
+
use(Pagination::Paginate)
|
32
|
+
|
33
|
+
def self.with_params(params)
|
34
|
+
new.with_params(params)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.permitted_params
|
38
|
+
super.excluding("sort", "page")
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :param_key
|
42
|
+
|
43
|
+
def initialize(param_key: :filters, **options)
|
44
|
+
super(**options)
|
45
|
+
|
46
|
+
@param_key = param_key.to_sym
|
47
|
+
end
|
48
|
+
|
49
|
+
def model_name
|
50
|
+
@model_name ||= items.model_name.tap do |name|
|
51
|
+
name.param_key = param_key
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def with_params(params)
|
56
|
+
params = params.permit(:sort, :page, param_key => self.class.permitted_params)
|
57
|
+
filters = params[param_key]
|
58
|
+
|
59
|
+
self.attributes = filters if filters.present?
|
60
|
+
self.attributes = params.slice("page", "sort")
|
61
|
+
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_params
|
66
|
+
if filtered?
|
67
|
+
{ param_key.to_s => super.except("sort", "page") }.merge(super.slice("sort", "page"))
|
68
|
+
else
|
69
|
+
super.slice("sort", "page")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def inspect
|
74
|
+
"#<#{self.class.name} @param_key=#{param_key.inspect} " +
|
75
|
+
"@attributes=#{attributes.inspect} @model=\"#{model}\" @count=#{items&.count}>"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Katalyst
|
4
4
|
module Tables
|
5
|
-
module
|
5
|
+
module Collection
|
6
6
|
# A FormObject (model) representing the sort state of controller for a given
|
7
7
|
# collection/parameter.
|
8
8
|
class SortForm
|
@@ -37,8 +37,8 @@ module Katalyst
|
|
37
37
|
# @param column [String, Symbol]
|
38
38
|
# @return [true, false]
|
39
39
|
def supports?(collection, column)
|
40
|
-
collection.respond_to?("order_by_#{column}") ||
|
41
|
-
collection.
|
40
|
+
scope_for(collection).respond_to?(:"order_by_#{column}") ||
|
41
|
+
model_for(collection).has_attribute?(column.to_s)
|
42
42
|
end
|
43
43
|
|
44
44
|
# Returns the current sort behaviour of the given column, for use as a
|
@@ -72,8 +72,8 @@ module Katalyst
|
|
72
72
|
def apply(collection)
|
73
73
|
return [self, collection] if column.nil?
|
74
74
|
|
75
|
-
if collection.respond_to?("order_by_#{column}")
|
76
|
-
collection = collection.reorder(nil).public_send("order_by_#{column}", direction.to_sym)
|
75
|
+
if collection.respond_to?(:"order_by_#{column}")
|
76
|
+
collection = collection.reorder(nil).public_send(:"order_by_#{column}", direction.to_sym)
|
77
77
|
elsif collection.model.has_attribute?(column)
|
78
78
|
collection = collection.reorder(column => direction)
|
79
79
|
else
|
@@ -88,6 +88,14 @@ module Katalyst
|
|
88
88
|
def clear!
|
89
89
|
self.column = self.direction = nil
|
90
90
|
end
|
91
|
+
|
92
|
+
def scope_for(collection)
|
93
|
+
collection.is_a?(Core) ? collection.items : collection
|
94
|
+
end
|
95
|
+
|
96
|
+
def model_for(collection)
|
97
|
+
scope_for(collection).model
|
98
|
+
end
|
91
99
|
end
|
92
100
|
end
|
93
101
|
end
|
data/lib/katalyst/tables.rb
CHANGED
@@ -3,11 +3,7 @@
|
|
3
3
|
require "view_component"
|
4
4
|
require "katalyst/html_attributes"
|
5
5
|
|
6
|
-
require_relative "tables/backend"
|
7
6
|
require_relative "tables/engine"
|
8
|
-
require_relative "tables/frontend"
|
9
|
-
|
10
|
-
require_relative "tables/engine" if Object.const_defined?(:Rails)
|
11
7
|
|
12
8
|
module Katalyst
|
13
9
|
module Tables
|
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.
|
4
|
+
version: 2.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katalyst Interactive
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: katalyst-html-attributes
|
@@ -38,8 +38,8 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
-
description:
|
42
|
-
|
41
|
+
description: HTML table generator for building tabular index views with filtering,
|
42
|
+
sorting, and pagination.
|
43
43
|
email:
|
44
44
|
- devs@katalyst.com.au
|
45
45
|
executables: []
|
@@ -70,33 +70,36 @@ files:
|
|
70
70
|
- app/components/katalyst/tables/pagy_nav_component.rb
|
71
71
|
- app/components/katalyst/turbo/pagy_nav_component.rb
|
72
72
|
- app/components/katalyst/turbo/table_component.rb
|
73
|
+
- app/controllers/concerns/katalyst/tables/backend.rb
|
74
|
+
- app/helpers/katalyst/tables/frontend.rb
|
75
|
+
- app/helpers/katalyst/tables/frontend/helper.rb
|
73
76
|
- app/javascript/tables/application.js
|
74
77
|
- app/javascript/tables/orderable/form_controller.js
|
75
78
|
- app/javascript/tables/orderable/item_controller.js
|
76
79
|
- app/javascript/tables/orderable/list_controller.js
|
77
|
-
- app/javascript/tables/turbo/
|
80
|
+
- app/javascript/tables/turbo/collection_controller.js
|
78
81
|
- app/models/concerns/katalyst/tables/collection/core.rb
|
82
|
+
- app/models/concerns/katalyst/tables/collection/filtering.rb
|
83
|
+
- app/models/concerns/katalyst/tables/collection/has_params.rb
|
79
84
|
- app/models/concerns/katalyst/tables/collection/pagination.rb
|
80
85
|
- app/models/concerns/katalyst/tables/collection/reducers.rb
|
81
86
|
- app/models/concerns/katalyst/tables/collection/sorting.rb
|
82
|
-
- app/models/katalyst/tables/collection.rb
|
87
|
+
- app/models/katalyst/tables/collection/base.rb
|
88
|
+
- app/models/katalyst/tables/collection/filter.rb
|
89
|
+
- app/models/katalyst/tables/collection/sort_form.rb
|
83
90
|
- config/importmap.rb
|
84
91
|
- config/locales/tables.en.yml
|
85
92
|
- lib/katalyst/tables.rb
|
86
|
-
- lib/katalyst/tables/backend.rb
|
87
|
-
- lib/katalyst/tables/backend/sort_form.rb
|
88
93
|
- lib/katalyst/tables/engine.rb
|
89
|
-
|
90
|
-
- lib/katalyst/tables/frontend/helper.rb
|
91
|
-
homepage: https://github.com/katalyst/katalyst-tables
|
94
|
+
homepage: https://github.com/katalyst/tables
|
92
95
|
licenses:
|
93
96
|
- MIT
|
94
97
|
metadata:
|
95
98
|
allowed_push_host: https://rubygems.org
|
96
99
|
rubygems_mfa_required: 'true'
|
97
|
-
homepage_uri: https://github.com/katalyst/
|
98
|
-
source_code_uri: https://github.com/katalyst/
|
99
|
-
changelog_uri: https://github.com/katalyst/
|
100
|
+
homepage_uri: https://github.com/katalyst/tables
|
101
|
+
source_code_uri: https://github.com/katalyst/tables
|
102
|
+
changelog_uri: https://github.com/katalyst/tables/blobs/main/CHANGELOG.md
|
100
103
|
post_install_message:
|
101
104
|
rdoc_options: []
|
102
105
|
require_paths:
|
File without changes
|
/data/app/javascript/tables/turbo/{turbo_collection_controller.js → collection_controller.js}
RENAMED
File without changes
|