1import { Controller } from "@hotwired/stimulus" 2import { patch } from "@rails/request.js" 3 4// Connects to data-controller="checkbox-group" 5// Reusable checkbox group: toggles visibility of elements matching 6// a configurable data attribute, and persists state via async PATCH. 7// 8// Values: 9// url â PATCH endpoint to persist hidden columns 10// column-attribute â data attribute name used to find table cells, e.g. "data-pixel-table-column" 11// 12// Targets: 13// checkbox â the checkboxes controlling visibility 14// counter â optional span showing "visible/total" count on the trigger button 15// modal â Bootstrap modal containing the checkboxes 16// sortableList â container for sortable checkboxes 17export default class extends Controller { 18 static targets = ["checkbox", "counter", "modal", "sortableList"] 19 static values = { url: String, columnAttribute: String, sortable: Boolean, refreshOnClose: Boolean } 20 21 connect() { 22 this.updateFirstVisibleAccordionBorder() 23 if (this.sortableValue) { 24 this._onModalShow = () => this._initSortable() 25 this.modalTarget.addEventListener("shown.bs.modal", this._onModalShow) 26 } 27 if (this.refreshOnCloseValue) { 28 this._onModalClose = () => { if (this._dirty) { this._dirty = false; window.location.reload() } } 29 this.modalTarget.addEventListener("hidden.bs.modal", this._onModalClose) 30 } 31 } 32 33 disconnect() { 34 if (this._onModalShow) this.modalTarget.removeEventListener("shown.bs.modal", this._onModalShow) 35 if (this._onModalClose) this.modalTarget.removeEventListener("hidden.bs.modal", this._onModalClose) 36 this.sortable?.destroy() 37 } 38 39 _initSortable() { 40 if (this.sortable) return 41 if (!this.hasSortableListTarget) return 42 this.sortable = Sortable.create(this.sortableListTarget, { 43 animation: 150, 44 handle: ".bi-grip-vertical", 45 onEnd: () => this.persist() 46 }) 47 } 48 49 toggle(event) { 50 this.applyVisibility(event.target.value, event.target.checked) 51 this.updateCounter() 52 this.persist() 53 } 54 55 checkAll(event) { 56 event.preventDefault() 57 this.checkboxTargets.forEach(cb => { 58 cb.checked = true 59 this.applyVisibility(cb.value, true) 60 }) 61 this.updateCounter() 62 this.persist() 63 } 64 65 uncheckAll(event) { 66 event.preventDefault() 67 this.checkboxTargets.forEach(cb => { 68 cb.checked = false 69 this.applyVisibility(cb.value, false) 70 }) 71 this.updateCounter() 72 this.persist() 73 } 74 75 applyVisibility(key, visible) { 76 const attr = this.columnAttributeValue || "data-column" 77 document.querySelectorAll(`[${attr}="${key}"]`).forEach(el => { 78 el.classList.toggle("d-none", !visible) 79 }) 80 this.updateFirstVisibleAccordionBorder() 81 } 82 83 updateFirstVisibleAccordionBorder() { 84 const attr = this.columnAttributeValue || "data-column" 85 const accordionItems = Array.from(document.querySelectorAll(`[${attr}]`)) 86 .filter(el => el.classList.contains("accordion-item")) 87 accordionItems.forEach(el => el.classList.remove("border-top")) 88 accordionItems.find(el => !el.classList.contains("d-none"))?.classList.add("border-top") 89 } 90 91 updateCounter() { 92 if (!this.hasCounterTarget) return 93 const total = this.checkboxTargets.length 94 const visible = this.checkboxTargets.filter(cb => cb.checked).length 95 this.counterTarget.textContent = `(${visible}/${total})` 96 } 97 98 persist() { 99 this._dirty = true 100 const checkboxes = this.checkboxTargets 101 const hidden = checkboxes.filter(cb => !cb.checked).map(cb => cb.value) 102 const ordered = checkboxes.map(cb => cb.value) 103 const data = new FormData() 104 hidden.forEach(col => data.append("hidden_columns[]", col)) 105 if (hidden.length === 0) data.append("hidden_columns[]", "") 106 ordered.forEach(col => data.append("ordered_columns[]", col)) 107 patch(this.urlValue, { body: data, responseKind: "turbo-stream" }) 108 } 109}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.