Skip to content

Instantly share code, notes, and snippets.

@dorner
Last active February 22, 2024 10:03
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dorner/e8d4f7b292cd9621d14e0080df0ef254 to your computer and use it in GitHub Desktop.
Save dorner/e8d4f7b292cd9621d14e0080df0ef254 to your computer and use it in GitHub Desktop.
Stimulus table controller
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", '&lt;', 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", '&gt;', 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