interface SortableItem {
item: Element;
value: string;
}
type config = {
showIndicators: boolean;
acsSvg: string;
descSvg: string;
SvgClass: string;
}
const upIcon = '';
const downIcon = '';
/**
* SortableTable allows you to sort a table by clicking on the headers.
* @class SortableTable
* @example
* const table = new SortableTable('.header-wrapper', '.list-element', 'sort-key', {
* showIndicators: true,
* acsSvg: upIcon,
* descSvg: downIcon,
* SvgClass: 'icon-class'
* });
* Here is an example of how to use the SortableTable class:
* We are creating a new instance of the SortableTable class and passing in the following parameters:
* .header-wrapper: The selector for the header wrapper element. This is div containing the table headers.
* .list-element: The selector for the list element. This is the table element that we want to sort. This must be the parent element of the rows that we want to sort.
* sort-key: The attribute key that we want to sort by. This is the attribute that we want to use to sort the rows. All headers inside the header wrapper must have this attribute and elements inside row elements must have this attribute as well.
*
* So if we have a header for name, we sould give it arritube data-sort="name" and the elements inside the row should have data-sort="name" as well.
*/
export class SortableTable {
private headerWrapper: Element;
private listElement: Element;
private sortKey: string;
private config: config;
private acsSvg: string = upIcon;
private descSvg: string = downIcon;
constructor(headerWrapperSelector: string, listElementSelector: string, sortKey: string, config: config) {
this.config = config;
this.headerWrapper = document.querySelector(headerWrapperSelector);
if (!this.headerWrapper) throw new Error('Header wrapper not found.');
this.listElement = document.querySelector(listElementSelector);
if (!this.listElement) throw new Error('List element not found.');
this.sortKey = sortKey;
this.init();
}
private init(): void {
const headers = this.headerWrapper.querySelectorAll(`[${this.sortKey}]`);
headers.forEach((header: Element) => {
header.setAttribute('sort-dr', 'asc'); // Initialize direction
header.addEventListener('click', () => {
this.sort(header);
});
});
}
private sort(headerElement: Element): void {
const attributeName = headerElement.getAttribute(this.sortKey);
if (!attributeName) throw new Error('Attribute name not found on header element.');
const items = Array.from(this.listElement.querySelectorAll("[thind=todo_item]"));
let dir: string = headerElement.getAttribute('sort-dr') || 'asc';
const itemsWithValues: SortableItem[] = items.map((item: Element): SortableItem => {
const element = item.querySelector(`[${this.sortKey}="${attributeName}"]`);
return {
item,
value: element ? element.textContent.toLowerCase() : ""
};
});
itemsWithValues.sort((a: SortableItem, b: SortableItem): number => {
if (a.value > b.value) return dir === 'asc' ? 1 : -1;
if (a.value < b.value) return dir === 'asc' ? -1 : 1;
return 0;
});
for (const { item } of itemsWithValues) {
this.listElement.appendChild(item);
}
// Toggle the direction for the next click
dir = dir === 'asc' ? 'desc' : 'asc';
headerElement.setAttribute('sort-dr', dir);
// Show indicators
if (this.config.showIndicators) {
const exsistingIcons = this.headerWrapper.querySelectorAll('[thind-sort-icon]');
exsistingIcons.forEach((icon) => icon.remove());
const svg = dir === 'asc' ? this.acsSvg : this.descSvg;
const svgClass = this.config.SvgClass;
const svgElement = document.createElement('span');
svgElement.innerHTML = svg;
if (svgClass){
svgElement.classList.add(svgClass);
}else{
svgElement.style.height = '12px';
svgElement.style.width = '12px';
svgElement.style.color = 'grey';
}
svgElement.setAttribute('thind-sort-icon', '');
headerElement.appendChild(svgElement);
}
}
}