Last active
February 22, 2024 10:03
-
-
Save dorner/e8d4f7b292cd9621d14e0080df0ef254 to your computer and use it in GitHub Desktop.
Stimulus table controller
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Controller } from "stimulus" | |
import $ from "jquery" | |
export default class extends Controller { | |
static targets = ["filter", "table", "pagination", "row"] | |
connect() { | |
this.page = Number(this.data.get("page")) || 1; | |
this.perPage = Number(this.data.get("per-page")) || 20; | |
this.filterRows(); | |
$(this.filterTargets).filter(':text').on('input', this.filterChanged.bind(this)); | |
$(this.filterTargets).filter(':checkbox').on('click', this.filterChanged.bind(this)); | |
$(this.paginationTarget).on('click', this.pageChanged.bind(this)); | |
} | |
pageChanged(event) { | |
if ($(event.target).hasClass("disabled")) return; | |
const page = $(event.target).data('page'); | |
if (page == 'prev') { this.page -= 1 } | |
else if (page == 'next') { this.page += 1 } | |
else this.page = page; | |
this.filterRows(); | |
} | |
filterChanged() { | |
this.page = 1; | |
this.filterRows(); | |
} | |
showRow(row, index, filterValues) { | |
const perPage = this.perPage; | |
const firstRow = (this.page - 1) * perPage; | |
const lastRow = firstRow + perPage; | |
// set shouldHide to true if any filter returns true (true in this case means "don't show") | |
const shouldHide = filterValues.some((filter) => { | |
const rowData = row.data(filter.field); | |
if (filter.type == 'search' && rowData && rowData.match && !rowData.match(filter.value)) { | |
return true; | |
} | |
else if (filter.type == 'checkbox' && filter.value && !rowData) { | |
return true; | |
} | |
}) | |
const isInView = index >= firstRow && index < lastRow; | |
return { valid: !shouldHide, show: isInView && !shouldHide }; | |
} | |
filterValues() { | |
return this.filterTargets.map((f) => { | |
const filter = $(f); | |
const field = filter.attr('name'); | |
let value = filter.val(); | |
let type = 'search'; | |
if (filter.is(':checkbox')) { | |
type = 'checkbox'; | |
value = filter.is(':checked') | |
} | |
return { field, type, value }; | |
}) | |
} | |
filterRows() { | |
const filterValues = this.filterValues(); | |
let index = 0; | |
this.rowTargets.forEach((tableRow) => { | |
const results = this.showRow($(tableRow), index, filterValues); | |
if (results.valid) index += 1; | |
tableRow.style.display = results.show ? "table-row" : "none"; | |
}) | |
this.entries = index; | |
this.paginate(); | |
} | |
paginate() { | |
const allPages = Math.ceil(this.entries / this.perPage); | |
const pages = this.calculateVisiblePages(allPages); | |
const parent = $(this.paginationTarget); | |
parent.html(''); | |
parent.append(this.navigationEntry("prev", '<', false, this.page == 1)); | |
let previousPage = null; | |
pages.forEach((page) => { | |
if (page - previousPage > 1) { | |
parent.append(this.navigationEntry('', '...', false, true)); | |
} | |
parent.append(this.navigationEntry(page, page, this.page == page, false)); | |
previousPage = page; | |
}) | |
parent.append(this.navigationEntry("next", '>', false, this.page == allPages)); | |
} | |
calculateVisiblePages(allPages) { | |
const visiblePages = new Set(); | |
visiblePages.add(1); | |
visiblePages.add(this.page); | |
if (allPages > 1) { | |
visiblePages.add(2); | |
visiblePages.add(allPages); | |
visiblePages.add(allPages - 1); | |
} | |
if (this.page > 1) { visiblePages.add(this.page - 1); } | |
if (this.page < allPages) { visiblePages.add(this.page + 1); } | |
return Array.from(visiblePages).sort((a,b) => a-b); | |
} | |
navigationEntry(value, label, active, disabled) { | |
return `<a data-page="${value}" class="item${active ? " active" : ""}${disabled ? " disabled" : ""}">${label}</a>` | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment