katalyst-tables 2.3.1 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16f4a66ac1715abeba201402eedf3c19f27dbd0c53291b841164ad79f085ec77
4
- data.tar.gz: 80aad00ee67b5d3d760a7b47ba3ffc735d704d49edb4e42942197c4d93ff8eea
3
+ metadata.gz: 5324d0bccadf307f48cb2199b8ba9123cb519756dda81cfc0c4c88a4abdfd886
4
+ data.tar.gz: ad299f1c8f62454aa3433b00cf4c0b69dacfe59cd6cfe598ab3ea2969cedecde
5
5
  SHA512:
6
- metadata.gz: 6713a3de4a8ee62071f3c34e1557691add7b02428eee13f994b107261571de84b6be57b9f1c7c997ab6bd87a23a4fecf68dfd52b311a412e26399b099fac4c0e
7
- data.tar.gz: 7fdd72501c7f780181fc2d3a0d182fd989c57a4aa4a4418a90a599c64fbc2bf1c960b27ebb8ce90ae9fdbdb9184c3020976d0f2b7cffbf9f19b5e704341e936c
6
+ metadata.gz: bd4daa1c09acaab6e85cbf787cfafb51303c9f030f26f15ca5f0a593da02c5dd526bf73d07b5cf6a99bfc267f2ea2a17d837e5eae81d607df2ad7788a9a99260
7
+ data.tar.gz: 143552907f2bf01ad256fc6e24b06404db1f173fe2ce3f4dca802eacd903fc5ab2f950a7c8c9ff6b086efe00c8838b8eca9dfbb2edcc0cc61d76717f6ca171ac
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [2.4.0]
4
+
5
+ - Internal refactor of filters to make it easier to add custom extensions
6
+ and define nested attributes.
7
+
3
8
  ## [2.3.0]
4
9
 
5
10
  - Remove support for Ruby < 3.0 (no longer used at Katalyst)
@@ -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
 
@@ -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
 
@@ -1 +1 @@
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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"}
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
- partial = @partial || model_name&.param_key&.to_s
28
- as = @as || model_name&.param_key&.to_sym
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::Backend::SortForm. If not provided will be inferred from the collection.
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
@@ -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,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "frontend/helper"
4
-
5
3
  module Katalyst
6
4
  module Tables
7
5
  # View Helper for generating HTML tables. Include in your ApplicationHelper, or similar.
@@ -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 :model, :each, :count, :empty?, to: :items, allow_nil: true
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 = Backend::SortForm.parse(sorting) if 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 = Backend::SortForm.parse(value, default: attribute_was(:sort))
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 Backend
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.model.has_attribute?(column.to_s)
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
@@ -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.3.1
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: 2023-12-15 00:00:00.000000000 Z
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: Builder-style HTML table generator for building tabular index views.
42
- Supports sorting by columns.
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
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
- - lib/katalyst/tables/frontend.rb
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/katalyst-tables
98
- source_code_uri: https://github.com/katalyst/katalyst-tables
99
- changelog_uri: https://github.com/katalyst/katalyst-tables/blobs/main/CHANGELOG.md
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: