katalyst-tables 2.6.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -1
- data/README.md +57 -213
- data/app/assets/builds/katalyst/tables.esm.js +17 -47
- data/app/assets/builds/katalyst/tables.js +17 -47
- data/app/assets/builds/katalyst/tables.min.js +1 -1
- data/app/assets/builds/katalyst/tables.min.js.map +1 -1
- data/app/assets/stylesheets/katalyst/tables/_index.scss +1 -0
- data/app/assets/stylesheets/katalyst/tables/_ordinal.scss +38 -0
- data/app/assets/stylesheets/katalyst/tables/_table.scss +123 -0
- data/app/assets/stylesheets/katalyst/tables/typed-columns/_boolean.scss +4 -0
- data/app/assets/stylesheets/katalyst/tables/typed-columns/_currency.scss +5 -0
- data/app/assets/stylesheets/katalyst/tables/typed-columns/_date.scss +4 -0
- data/app/assets/stylesheets/katalyst/tables/typed-columns/_datetime.scss +4 -0
- data/app/assets/stylesheets/katalyst/tables/typed-columns/_index.scss +5 -0
- data/app/assets/stylesheets/katalyst/tables/typed-columns/_number.scss +5 -0
- data/app/components/concerns/katalyst/tables/has_table_content.rb +17 -8
- data/app/components/concerns/katalyst/tables/identifiable.rb +51 -0
- data/app/components/concerns/katalyst/tables/orderable.rb +35 -105
- data/app/components/concerns/katalyst/tables/selectable.rb +18 -74
- data/app/components/concerns/katalyst/tables/sortable.rb +51 -17
- data/app/components/katalyst/table_component.html.erb +4 -4
- data/app/components/katalyst/table_component.rb +277 -47
- data/app/components/katalyst/tables/body_row_component.html.erb +5 -0
- data/app/components/katalyst/tables/body_row_component.rb +4 -24
- data/app/components/katalyst/tables/cell_component.rb +85 -0
- data/app/components/katalyst/tables/cells/boolean_component.rb +20 -0
- data/app/components/katalyst/tables/cells/currency_component.rb +29 -0
- data/app/components/katalyst/tables/cells/date_component.rb +67 -0
- data/app/components/katalyst/tables/cells/date_time_component.rb +60 -0
- data/app/components/katalyst/tables/cells/number_component.rb +22 -0
- data/app/components/katalyst/tables/cells/ordinal_component.rb +44 -0
- data/app/components/katalyst/tables/cells/rich_text_component.rb +21 -0
- data/app/components/katalyst/tables/cells/select_component.rb +39 -0
- data/app/components/katalyst/tables/data.rb +30 -0
- data/app/components/katalyst/tables/empty_caption_component.rb +1 -1
- data/app/components/katalyst/tables/header_row_component.html.erb +5 -0
- data/app/components/katalyst/tables/header_row_component.rb +4 -24
- data/app/components/katalyst/tables/label.rb +37 -0
- data/app/components/katalyst/tables/orderable/form_component.rb +38 -0
- data/app/components/katalyst/tables/selectable/form_component.html.erb +6 -4
- data/app/components/katalyst/tables/selectable/form_component.rb +8 -11
- data/app/controllers/concerns/katalyst/tables/backend.rb +2 -28
- data/app/helpers/katalyst/tables/frontend.rb +48 -2
- data/app/javascript/tables/application.js +0 -5
- data/app/javascript/tables/orderable/form_controller.js +8 -6
- data/app/javascript/tables/orderable/item_controller.js +9 -0
- data/app/models/concerns/katalyst/tables/collection/core.rb +6 -1
- data/app/models/concerns/katalyst/tables/collection/pagination.rb +8 -1
- data/app/models/concerns/katalyst/tables/collection/sorting.rb +85 -17
- data/app/models/katalyst/tables/collection/array.rb +38 -0
- data/app/models/katalyst/tables/collection/base.rb +4 -0
- data/lib/katalyst/tables/config.rb +23 -0
- data/lib/katalyst/tables.rb +9 -0
- metadata +32 -15
- data/app/components/concerns/katalyst/tables/configurable_component.rb +0 -52
- data/app/components/concerns/katalyst/tables/turbo_replaceable.rb +0 -79
- data/app/components/katalyst/tables/body_cell_component.rb +0 -47
- data/app/components/katalyst/tables/header_cell_component.rb +0 -65
- data/app/components/katalyst/turbo/pagy_nav_component.rb +0 -23
- data/app/components/katalyst/turbo/table_component.rb +0 -45
- data/app/helpers/katalyst/tables/frontend/helper.rb +0 -31
- data/app/javascript/tables/turbo/collection_controller.js +0 -38
- data/app/models/katalyst/tables/collection/sort_form.rb +0 -102
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"tables.min.js","sources":["../../../javascript/tables/orderable/list_controller.js","../../../javascript/tables/selection/item_controller.js","../../../javascript/tables/application.js","../../../javascript/tables/turbo/collection_controller.js","../../../javascript/tables/orderable/item_controller.js","../../../javascript/tables/orderable/form_controller.js","../../../javascript/tables/selection/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 { Controller } from \"@hotwired/stimulus\";\n\nexport default class SelectionItemController extends Controller {\n static outlets = [\"tables--selection--form\"];\n static values = {\n params: Object,\n checked: Boolean,\n };\n\n tablesSelectionFormOutletConnected(form) {\n this.checkedValue = form.isSelected(this.id);\n }\n\n change(e) {\n e.preventDefault();\n\n this.checkedValue = this.tablesSelectionFormOutlet.toggle(this.id);\n }\n\n get id() {\n return this.paramsValue.id;\n }\n\n checkedValueChanged(checked) {\n this.element.querySelector(\"input\").checked = checked;\n }\n}\n","import TurboCollectionController from \"./turbo/collection_controller\";\nimport OrderableItemController from \"./orderable/item_controller\";\nimport OrderableListController from \"./orderable/list_controller\";\nimport OrderableFormController from \"./orderable/form_controller\";\nimport SelectionFormController from \"./selection/form_controller\";\nimport SelectionItemController from \"./selection/item_controller\";\n\nconst Definitions = [\n {\n identifier: \"tables--turbo--collection\",\n controllerConstructor: TurboCollectionController,\n },\n {\n identifier: \"tables--orderable--item\",\n controllerConstructor: OrderableItemController,\n },\n {\n identifier: \"tables--orderable--list\",\n controllerConstructor: OrderableListController,\n },\n {\n identifier: \"tables--orderable--form\",\n controllerConstructor: OrderableFormController,\n },\n {\n identifier: \"tables--selection--form\",\n controllerConstructor: SelectionFormController,\n },\n {\n identifier: \"tables--selection--item\",\n controllerConstructor: SelectionItemController,\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","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class SelectionFormController extends Controller {\n static values = {\n count: Number,\n primaryKey: { type: String, default: \"id\" },\n };\n static targets = [\"count\", \"singular\", \"plural\"];\n\n connect() {\n this.countValue = this.inputs.length;\n }\n\n /**\n * @param id to toggle\n * @return {boolean} true if selected, false if unselected\n */\n toggle(id) {\n const input = this.input(id);\n\n if (input) {\n input.remove();\n } else {\n this.element.insertAdjacentHTML(\n \"beforeend\",\n `<input type=\"hidden\" name=\"${this.primaryKeyValue}[]\" value=\"${id}\">`,\n );\n }\n\n this.countValue = this.inputs.length;\n\n return !input;\n }\n\n /**\n * @returns {boolean} true if the given id is currently selected\n */\n isSelected(id) {\n return !!this.input(id);\n }\n\n get inputs() {\n return this.element.querySelectorAll(\n `input[name=\"${this.primaryKeyValue}[]\"]`,\n );\n }\n\n input(id) {\n return this.element.querySelector(\n `input[name=\"${this.primaryKeyValue}[]\"][value=\"${id}\"]`,\n );\n }\n\n countValueChanged(count) {\n this.element.toggleAttribute(\"hidden\", count === 0);\n this.countTarget.textContent = count;\n this.singularTarget.toggleAttribute(\"hidden\", count !== 1);\n this.pluralTarget.toggleAttribute(\"hidden\", count === 1);\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","SelectionItemController","Controller","static","params","Object","checked","Boolean","tablesSelectionFormOutletConnected","form","checkedValue","isSelected","change","e","preventDefault","tablesSelectionFormOutlet","toggle","paramsValue","checkedValueChanged","element","querySelector","Definitions","identifier","controllerConstructor","query","String","sort","queryValueChanged","Turbo","navigator","history","replace","url","sortValueChanged","document","querySelectorAll","sortSelector","forEach","input","value","frame","closest","URL","baseURI","window","location","href","search","connect","index","Array","from","parentElement","children","indexOf","paramsValueChanged","id_value","dragUpdate","offset","dragOffset","style","zIndex","toggleAttribute","updateVisually","dragIndex","updateIndex","reset","removeAttribute","hasChanges","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","animate","ticking","requestAnimationFrame","tablesOrderableFormOutlets","tablesOrderableFormOutletConnected","tablesOrderableFormOutletDisconnected","currentItems","find","toSorted","a","b","id_name","index_name","insertAdjacentHTML","inputs","length","requestSubmit","remove","count","Number","primaryKey","type","default","countValue","primaryKeyValue","countValueChanged","countTarget","textContent","singularTarget","pluralTarget"],"mappings":"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"}
|
1
|
+
{"version":3,"file":"tables.min.js","sources":["../../../javascript/tables/orderable/list_controller.js","../../../javascript/tables/selection/item_controller.js","../../../javascript/tables/application.js","../../../javascript/tables/orderable/item_controller.js","../../../javascript/tables/orderable/form_controller.js","../../../javascript/tables/selection/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 { Controller } from \"@hotwired/stimulus\";\n\nexport default class SelectionItemController extends Controller {\n static outlets = [\"tables--selection--form\"];\n static values = {\n params: Object,\n checked: Boolean,\n };\n\n tablesSelectionFormOutletConnected(form) {\n this.checkedValue = form.isSelected(this.id);\n }\n\n change(e) {\n e.preventDefault();\n\n this.checkedValue = this.tablesSelectionFormOutlet.toggle(this.id);\n }\n\n get id() {\n return this.paramsValue.id;\n }\n\n checkedValueChanged(checked) {\n this.element.querySelector(\"input\").checked = checked;\n }\n}\n","import OrderableItemController from \"./orderable/item_controller\";\nimport OrderableListController from \"./orderable/list_controller\";\nimport OrderableFormController from \"./orderable/form_controller\";\nimport SelectionFormController from \"./selection/form_controller\";\nimport SelectionItemController from \"./selection/item_controller\";\n\nconst Definitions = [\n {\n identifier: \"tables--orderable--item\",\n controllerConstructor: OrderableItemController,\n },\n {\n identifier: \"tables--orderable--list\",\n controllerConstructor: OrderableListController,\n },\n {\n identifier: \"tables--orderable--form\",\n controllerConstructor: OrderableFormController,\n },\n {\n identifier: \"tables--selection--form\",\n controllerConstructor: SelectionFormController,\n },\n {\n identifier: \"tables--selection--item\",\n controllerConstructor: SelectionItemController,\n },\n];\n\nexport { Definitions as default };\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 /** Retrieve params for use in the form */\n params(scope) {\n const { id_name, id_value, index_name } = this.paramsValue;\n return [\n { name: `${scope}[${id_value}][${id_name}]`, value: this.id },\n { name: `${scope}[${id_value}][${index_name}]`, value: this.index },\n ];\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 static values = { scope: String };\n\n add(item) {\n item.params(this.scopeValue).forEach(({ name, value }) => {\n this.element.insertAdjacentHTML(\n \"beforeend\",\n `<input type=\"hidden\" name=\"${name}\" value=\"${value}\" data-generated>`,\n );\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","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class SelectionFormController extends Controller {\n static values = {\n count: Number,\n primaryKey: { type: String, default: \"id\" },\n };\n static targets = [\"count\", \"singular\", \"plural\"];\n\n connect() {\n this.countValue = this.inputs.length;\n }\n\n /**\n * @param id to toggle\n * @return {boolean} true if selected, false if unselected\n */\n toggle(id) {\n const input = this.input(id);\n\n if (input) {\n input.remove();\n } else {\n this.element.insertAdjacentHTML(\n \"beforeend\",\n `<input type=\"hidden\" name=\"${this.primaryKeyValue}[]\" value=\"${id}\">`,\n );\n }\n\n this.countValue = this.inputs.length;\n\n return !input;\n }\n\n /**\n * @returns {boolean} true if the given id is currently selected\n */\n isSelected(id) {\n return !!this.input(id);\n }\n\n get inputs() {\n return this.element.querySelectorAll(\n `input[name=\"${this.primaryKeyValue}[]\"]`,\n );\n }\n\n input(id) {\n return this.element.querySelector(\n `input[name=\"${this.primaryKeyValue}[]\"][value=\"${id}\"]`,\n );\n }\n\n countValueChanged(count) {\n this.element.toggleAttribute(\"hidden\", count === 0);\n this.countTarget.textContent = count;\n this.singularTarget.toggleAttribute(\"hidden\", count !== 1);\n this.pluralTarget.toggleAttribute(\"hidden\", count === 1);\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","SelectionItemController","Controller","static","params","Object","checked","Boolean","tablesSelectionFormOutletConnected","form","checkedValue","isSelected","change","e","preventDefault","tablesSelectionFormOutlet","toggle","paramsValue","checkedValueChanged","element","querySelector","Definitions","identifier","controllerConstructor","connect","index","Array","from","parentElement","children","indexOf","paramsValueChanged","id_value","dragUpdate","offset","dragOffset","style","zIndex","toggleAttribute","updateVisually","dragIndex","updateIndex","scope","id_name","index_name","name","value","reset","removeAttribute","hasChanges","index_value","round","comparisonIndex","startDragging","dragState","document","addEventListener","mousemove","mouseup","window","scroll","stopDragging","removeEventListener","tablesOrderableItemOutlets","forEach","item","drop","dragItem","newIndex","targetItem","insertAdjacentElement","commitChanges","tablesOrderableFormOutlet","clear","add","submit","mousedown","isDragging","animate","ticking","requestAnimationFrame","tablesOrderableFormOutlets","tablesOrderableFormOutletConnected","tablesOrderableFormOutletDisconnected","currentItems","find","toSorted","a","b","String","scopeValue","insertAdjacentHTML","inputs","length","requestSubmit","input","remove","querySelectorAll","count","Number","primaryKey","type","default","countValue","primaryKeyValue","countValueChanged","countTarget","textContent","singularTarget","pluralTarget"],"mappings":"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"}
|
@@ -0,0 +1 @@
|
|
1
|
+
@use "table";
|
@@ -0,0 +1,38 @@
|
|
1
|
+
$width: 2rem !default;
|
2
|
+
$time: 125ms !default;
|
3
|
+
|
4
|
+
table {
|
5
|
+
tr {
|
6
|
+
transition:
|
7
|
+
top $time ease-in-out,
|
8
|
+
transform $time ease-in-out;
|
9
|
+
}
|
10
|
+
|
11
|
+
tr[dragging] {
|
12
|
+
transition: transform $time ease-in-out;
|
13
|
+
filter: drop-shadow(0 0 0.5rem var(--row-border-color));
|
14
|
+
transform: scale(1.01);
|
15
|
+
td {
|
16
|
+
box-shadow: none;
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
th.ordinal {
|
21
|
+
width: $width;
|
22
|
+
padding-left: 0;
|
23
|
+
a {
|
24
|
+
width: $width;
|
25
|
+
height: 3rem;
|
26
|
+
}
|
27
|
+
a::after {
|
28
|
+
right: 0;
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
td.ordinal {
|
33
|
+
width: $width;
|
34
|
+
padding-left: 0;
|
35
|
+
cursor: grab;
|
36
|
+
text-align: center;
|
37
|
+
}
|
38
|
+
}
|
@@ -0,0 +1,123 @@
|
|
1
|
+
@use "ordinal" as *;
|
2
|
+
@use "typed-columns";
|
3
|
+
|
4
|
+
$grey: #f0ecf3 !default;
|
5
|
+
|
6
|
+
$table-header-color: transparent !default;
|
7
|
+
$row-border-color: $grey !default;
|
8
|
+
$row-height: 48px !default;
|
9
|
+
$cell-spacing: 0.5rem !default;
|
10
|
+
|
11
|
+
$width-small: 6rem !default;
|
12
|
+
$width-medium: 12rem !default;
|
13
|
+
$width-large: 16rem !default;
|
14
|
+
|
15
|
+
table {
|
16
|
+
--row-height: #{$row-height};
|
17
|
+
--cell-spacing: #{$cell-spacing};
|
18
|
+
--table-header-color: #{$table-header-color};
|
19
|
+
--row-border-color: #{$row-border-color};
|
20
|
+
|
21
|
+
--width-small: #{$width-small};
|
22
|
+
--width-medium: #{$width-medium};
|
23
|
+
--width-large: #{$width-large};
|
24
|
+
}
|
25
|
+
|
26
|
+
table {
|
27
|
+
border: none;
|
28
|
+
table-layout: fixed;
|
29
|
+
border-collapse: collapse;
|
30
|
+
text-align: left;
|
31
|
+
width: 100%;
|
32
|
+
|
33
|
+
thead {
|
34
|
+
background: var(--table-header-color);
|
35
|
+
}
|
36
|
+
|
37
|
+
tr {
|
38
|
+
height: var(--row-height);
|
39
|
+
line-height: var(--row-height);
|
40
|
+
}
|
41
|
+
|
42
|
+
th,
|
43
|
+
td {
|
44
|
+
border: none;
|
45
|
+
box-shadow: inset 0px -1px 0px var(--row-border-color);
|
46
|
+
overflow: hidden;
|
47
|
+
text-overflow: ellipsis;
|
48
|
+
vertical-align: top;
|
49
|
+
white-space: nowrap;
|
50
|
+
background-color: white;
|
51
|
+
padding-right: var(--cell-spacing);
|
52
|
+
|
53
|
+
&:last-child {
|
54
|
+
padding-right: 0;
|
55
|
+
}
|
56
|
+
|
57
|
+
> a {
|
58
|
+
display: block;
|
59
|
+
overflow: hidden;
|
60
|
+
white-space: nowrap;
|
61
|
+
text-overflow: ellipsis;
|
62
|
+
text-decoration: none;
|
63
|
+
}
|
64
|
+
|
65
|
+
> img,
|
66
|
+
> a > img {
|
67
|
+
max-height: 3rem;
|
68
|
+
padding: 0;
|
69
|
+
}
|
70
|
+
|
71
|
+
> .trix-content {
|
72
|
+
overflow: hidden;
|
73
|
+
text-overflow: ellipsis;
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
th {
|
78
|
+
font-weight: bold;
|
79
|
+
|
80
|
+
:where(&.width-s) {
|
81
|
+
width: var(--width-small);
|
82
|
+
}
|
83
|
+
|
84
|
+
:where(&.width-m) {
|
85
|
+
width: var(--width-medium);
|
86
|
+
}
|
87
|
+
|
88
|
+
:where(&.width-l) {
|
89
|
+
width: var(--width-large);
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
thead a.ascending:after,
|
94
|
+
[data-sort="asc"] a::after {
|
95
|
+
display: inline-block;
|
96
|
+
content: " ";
|
97
|
+
position: relative;
|
98
|
+
margin-left: 1rem;
|
99
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 13'%3E%3Cpath d='M.541 0l11.125 12.573a.5.5 0 00.749 0L23.541 0h-23z' fill='%23000' fill-rule='evenodd'/%3E%3C/svg%3E");
|
100
|
+
background-size: 14px 14px;
|
101
|
+
height: 14px;
|
102
|
+
width: 14px;
|
103
|
+
top: 0;
|
104
|
+
transform: rotate(180deg);
|
105
|
+
}
|
106
|
+
|
107
|
+
thead a.descending:after,
|
108
|
+
[data-sort="desc"] a::after {
|
109
|
+
display: inline-block;
|
110
|
+
content: " ";
|
111
|
+
position: relative;
|
112
|
+
margin-left: 1rem;
|
113
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 13'%3E%3Cpath d='M.541 0l11.125 12.573a.5.5 0 00.749 0L23.541 0h-23z' fill='%23000' fill-rule='evenodd'/%3E%3C/svg%3E");
|
114
|
+
background-size: 14px 14px;
|
115
|
+
height: 14px;
|
116
|
+
width: 14px;
|
117
|
+
top: 4px;
|
118
|
+
}
|
119
|
+
|
120
|
+
caption {
|
121
|
+
margin: 2rem 0;
|
122
|
+
}
|
123
|
+
}
|
@@ -13,21 +13,30 @@ module Katalyst
|
|
13
13
|
@as = as
|
14
14
|
end
|
15
15
|
|
16
|
+
def before_render
|
17
|
+
# move @__vc_render_in_block to @row_proc to avoid slot lookup attempting to call it
|
18
|
+
@row_proc = @__vc_render_in_block
|
19
|
+
@__vc_render_in_block = nil
|
20
|
+
end
|
21
|
+
|
16
22
|
def model_name
|
17
23
|
collection.model_name if collection.respond_to?(:model_name)
|
18
24
|
end
|
19
25
|
|
20
26
|
private
|
21
27
|
|
28
|
+
def row_content(row, record)
|
29
|
+
@current_row = row
|
30
|
+
@current_record = record
|
31
|
+
row_proc.call(self, record)
|
32
|
+
ensure
|
33
|
+
@current_row = nil
|
34
|
+
@current_record = nil
|
35
|
+
end
|
36
|
+
|
22
37
|
def row_proc
|
23
|
-
|
24
|
-
|
25
|
-
elsif @__vc_render_in_block
|
26
|
-
@row_proc = @__vc_render_in_block
|
27
|
-
else
|
28
|
-
@row_proc = Proc.new do |row, object|
|
29
|
-
row_renderer.render_row(row, object, view_context)
|
30
|
-
end
|
38
|
+
@row_proc ||= Proc.new do |table, object|
|
39
|
+
row_renderer.render_row(table, object, view_context)
|
31
40
|
end
|
32
41
|
end
|
33
42
|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
# Adds dom ids to the table and row components.
|
6
|
+
# See [documentation](/docs/identifiable.md) for more details.
|
7
|
+
module Identifiable
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
module Defaults
|
11
|
+
extend self
|
12
|
+
|
13
|
+
# Returns the default dom id for the table, uses the collection's
|
14
|
+
# model name's route_key as a sensible default.
|
15
|
+
def default_table_id(collection = self.collection)
|
16
|
+
collection.model_name.route_key
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
included do
|
21
|
+
include Defaults
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(generate_ids: false, **)
|
25
|
+
super(**)
|
26
|
+
|
27
|
+
@generate_ids = generate_ids
|
28
|
+
end
|
29
|
+
|
30
|
+
def identifiable?
|
31
|
+
@generate_ids
|
32
|
+
end
|
33
|
+
|
34
|
+
def id
|
35
|
+
html_attributes[:id]
|
36
|
+
end
|
37
|
+
|
38
|
+
def before_render
|
39
|
+
if identifiable?
|
40
|
+
update_html_attributes(id: default_table_id(collection)) if id.nil?
|
41
|
+
|
42
|
+
@body_row_callbacks << Proc.new do |row, record|
|
43
|
+
row.update_html_attributes(id: dom_id(record))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -11,122 +11,52 @@ module Katalyst
|
|
11
11
|
ITEM_CONTROLLER = "tables--orderable--item"
|
12
12
|
LIST_CONTROLLER = "tables--orderable--list"
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
included do
|
19
|
-
# Add `orderable` slot to table component
|
20
|
-
config_component :orderable, default: "Katalyst::Tables::Orderable::FormComponent"
|
21
|
-
renders_one(:orderable, lambda do |**attrs|
|
22
|
-
orderable_component.new(table: self, **attrs)
|
23
|
-
end)
|
14
|
+
# Returns the default dom id for the selection form, uses the table's
|
15
|
+
# default id with '_selection' appended.
|
16
|
+
def self.default_form_id(collection)
|
17
|
+
"#{Identifiable::Defaults.default_table_id(collection)}_order_form"
|
24
18
|
end
|
25
19
|
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
table.extend(TableMethods)
|
30
|
-
|
31
|
-
# ensure row components support orderable column calls
|
32
|
-
table.send(:add_orderable_columns)
|
20
|
+
# Generate a form input 'name' for param updates for a given record and attribute.
|
21
|
+
def self.default_scope(collection)
|
22
|
+
"order[#{collection.model_name.plural}]"
|
33
23
|
end
|
34
24
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
#
|
39
|
-
add_orderable_columns
|
25
|
+
# Generate a nested scope for for param updates for a given record and attribute.
|
26
|
+
# Will be concatenated with the form's scope in the browser.
|
27
|
+
def self.record_scope(id, attribute)
|
28
|
+
"[#{id}][#{attribute}]"
|
40
29
|
end
|
41
30
|
|
42
|
-
|
43
|
-
|
31
|
+
# Generates a column for the user to drag and drop to reorder data rows.
|
32
|
+
#
|
33
|
+
# @param column [Symbol] the value to update when the user reorders the rows
|
34
|
+
# @param primary_key [Symbol] key for identifying rows that have changed in params (:id by default)
|
35
|
+
# @param ** [Hash] HTML attributes to be added to column cells
|
36
|
+
# @param & [Proc] optional block to wrap the cell content
|
37
|
+
# @return [void]
|
38
|
+
#
|
39
|
+
# @example Render a column with a drag-and-drop handle for users to reorder rows
|
40
|
+
# <% row.ordinal %> # label => <th></th>, data => <td ...>⠿</td>
|
41
|
+
def ordinal(column = :ordinal, primary_key: :id, **, &)
|
42
|
+
initialize_orderable if row.header?
|
44
43
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
mousedown->#{LIST_CONTROLLER}#mousedown
|
49
|
-
ACTIONS
|
50
|
-
"#{LIST_CONTROLLER}-#{FORM_CONTROLLER}-outlet" => "##{orderable.id}",
|
51
|
-
"#{LIST_CONTROLLER}-#{ITEM_CONTROLLER}-outlet" => "td.ordinal" } },
|
52
|
-
)
|
44
|
+
with_cell(Cells::OrdinalComponent.new(
|
45
|
+
collection:, row:, column:, record:, label: "", heading: false, primary_key:, **,
|
46
|
+
), &)
|
53
47
|
end
|
54
48
|
|
55
49
|
private
|
56
50
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
@orderable = FormComponent.new(table: self, **attrs)
|
67
|
-
|
68
|
-
self
|
69
|
-
end
|
70
|
-
|
71
|
-
def orderable?
|
72
|
-
@orderable.present?
|
73
|
-
end
|
74
|
-
|
75
|
-
def orderable
|
76
|
-
@orderable
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
module HeaderRow # :nodoc:
|
81
|
-
def ordinal(attribute = :ordinal, **)
|
82
|
-
cell(attribute, class: "ordinal", label: "")
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
module BodyRow # :nodoc:
|
87
|
-
def ordinal(attribute = :ordinal, primary_key: :id)
|
88
|
-
id = @record.public_send(primary_key)
|
89
|
-
params = {
|
90
|
-
id_name: @table.orderable.record_scope(id, primary_key),
|
91
|
-
id_value: id,
|
92
|
-
index_name: @table.orderable.record_scope(id, attribute),
|
93
|
-
index_value: @record.public_send(attribute),
|
94
|
-
}
|
95
|
-
cell(attribute, class: "ordinal", draggable: true, data: {
|
96
|
-
controller: ITEM_CONTROLLER,
|
97
|
-
"#{ITEM_CONTROLLER}-params-value": params.to_json,
|
98
|
-
}) { t("katalyst.tables.orderable.value") }
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
class FormComponent < ViewComponent::Base # :nodoc:
|
103
|
-
attr_reader :id, :url, :scope
|
104
|
-
|
105
|
-
def initialize(table:,
|
106
|
-
url:,
|
107
|
-
id: "#{table.id}-orderable-form",
|
108
|
-
scope: "order[#{table.collection.model_name.plural}]")
|
109
|
-
super
|
110
|
-
|
111
|
-
@table = table
|
112
|
-
@id = id
|
113
|
-
@url = url
|
114
|
-
@scope = scope
|
115
|
-
end
|
116
|
-
|
117
|
-
def record_scope(id, attribute)
|
118
|
-
"#{scope}[#{id}][#{attribute}]"
|
119
|
-
end
|
120
|
-
|
121
|
-
def call
|
122
|
-
form_with(id:, url:, method: :patch, data: { controller: FORM_CONTROLLER }) do |form|
|
123
|
-
form.button(hidden: "")
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def inspect
|
128
|
-
"#<#{self.class.name} id: #{id.inspect}, url: #{url.inspect}, scope: #{scope.inspect}>"
|
129
|
-
end
|
51
|
+
def initialize_orderable
|
52
|
+
update_tbody_attributes(
|
53
|
+
data: {
|
54
|
+
controller: LIST_CONTROLLER,
|
55
|
+
action: "mousedown->#{LIST_CONTROLLER}#mousedown",
|
56
|
+
"#{LIST_CONTROLLER}-#{FORM_CONTROLLER}-outlet" => "##{Orderable.default_form_id(collection)}",
|
57
|
+
"#{LIST_CONTROLLER}-#{ITEM_CONTROLLER}-outlet" => "td.ordinal",
|
58
|
+
},
|
59
|
+
)
|
130
60
|
end
|
131
61
|
end
|
132
62
|
end
|