G
Glide
0
Filter and sort widgets like Excel
N
nhipwell
Jul 24, 2024

see title

Comments
N
nhipwell
May 28, 2025

(function () {
'use strict';
const WIDGET_IDS = ["widget_532", "widget_533"];

if (window.location.href.includes('dashboard')){  
    WIDGET_IDS.forEach(WIDGET_ID => {  
        const interval = setInterval(() => {  
            const table = document.querySelector(`#${WIDGET_ID} table.table`);  
            if (table) {  
                clearInterval(interval);  
                addColumnFilters(table);  
            }  
        }, 500);  
    });  
}  

function addColumnFilters(table) {  
    const headerRow = table.querySelector('thead tr');  
    if (!headerRow) return;  

    const columns = headerRow.children.length;  
    const tbody = table.querySelector('tbody');  
    const rows = Array.from(tbody.querySelectorAll('tr')).filter(row => row.children.length === columns);  

    for (let col = 0; col < columns; col++) {  
        const th = headerRow.children[col];  
        if (th.querySelector('.filter-container')) continue;  

        const container = document.createElement('div');  
        container.className = 'filter-container';  
        container.style.display = 'inline-block';  
        container.style.position = 'relative';  

        const dot = document.createElement('span');  
        dot.textContent = 'โ—';  
        dot.style.marginLeft = '4px';  
        dot.style.fontWeight = 'bold';  
        dot.style.color = 'green';  
        container.appendChild(dot);  

        const button = document.createElement('button');  
        button.textContent = 'โ–ผ';  
        button.style.fontSize = 'smaller';  
        button.style.marginLeft = '4px';  
        container.appendChild(button);  

        const menu = document.createElement('div');  
        menu.style.position = 'absolute';  
        menu.style.top = '100%';  
        menu.style.left = 0;  
        menu.style.backgroundColor = 'white';  
        menu.style.border = '1px solid #ccc';  
        menu.style.zIndex = 1000;  
        menu.style.padding = '4px';  
        menu.style.display = 'none';  
        menu.style.maxHeight = '200px';  
        menu.style.overflowY = 'auto';  
        menu.style.width = '200px';  
        menu.classList.add('NOH_Menu')  

        const uniqueValues = Array.from(new Set(  
          rows.map(row => row.children[col]?.innerText.trim())  
        )).sort();  
        const selections = new Set(uniqueValues);  
        const updateDot = () => {  
            dot.style.color = (selections.size === uniqueValues.length) ? 'green' : 'red';  
        };  

        const checkboxElements = [];  

        const deselectAll = document.createElement('div');  
        deselectAll.classList.add("NOH_selector");  
        deselectAll.textContent = 'Deselect All';  
        deselectAll.style.cursor = 'pointer';  
        deselectAll.style.color = 'blue';  
        deselectAll.style.marginBottom = '4px';  
        deselectAll.onclick = () => {  
            if (deselectAll.textContent == 'Deselect All'){  
                checkboxElements.forEach(cb => {  
                    cb.checked = false;  
                    selections.delete(cb.value);  
                });  
                deselectAll.textContent ='Select All'  
            }else if (deselectAll.textContent == 'Select All'){  
                checkboxElements.forEach(cb => {  
                    cb.checked = true;  
                    selections.add(cb.value);  
                });  
                deselectAll.textContent = 'Deselect All';  
            }  
            updateDot();  
            filterTable(table);  
        };  
        menu.appendChild(deselectAll);  

        uniqueValues.forEach(value => {  
            const label = document.createElement('label');  
            label.style.display = 'block';  
            const checkbox = document.createElement('input');  
            checkbox.type = 'checkbox';  
            checkbox.value = value;  
            checkbox.checked = true;  
            checkbox.onchange = () => {  
                if (checkbox.checked) selections.add(value);  
                else selections.delete(value);  
                updateDot();  
                filterTable(table);  
            };  
            checkboxElements.push(checkbox);  
            label.appendChild(checkbox);  
            label.appendChild(document.createTextNode(' ' + value));  
            menu.appendChild(label);  
        });  

        document.addEventListener('click', e => {  
            if (!container.contains(e.target)) menu.style.display = 'none';  
        });  

        button.onclick = (e) => {  
            Array.from(document.getElementsByClassName('NOH_Menu')).forEach(el => {  
              el.style.display = 'none';  
            });  
            e.stopPropagation();  
            menu.style.display = menu.style.display === 'none' ? 'block' : 'none';  
        };  

        container.appendChild(menu);  
        th.appendChild(container);  

        th.dataset.colIndex = col;  
        th.dataset.filterSelections = selections;  
    }  

    filterTable(table);  
    enableRightClickFilter(table);  
}  
function enableRightClickFilter(table) {  
    const contextMenu = document.createElement('div');  
    contextMenu.style.position = 'absolute';  
    contextMenu.style.background = 'white';  
    contextMenu.style.border = '1px solid #ccc';  
    contextMenu.style.padding = '0';  
    contextMenu.style.zIndex = 10000;  
    contextMenu.style.display = 'none';  
    contextMenu.style.fontFamily = 'sans-serif';  
    document.body.appendChild(contextMenu);  

    let lastTarget = null;  

    function createMenuItem(text, onClick) {  
        const item = document.createElement('div');  
        item.textContent = text;  
        item.style.padding = '6px 12px';  
        item.style.cursor = 'pointer';  
        item.onmouseenter = () => item.style.background = '#eee';  
        item.onmouseleave = () => item.style.background = '';  
        item.onclick = () => {  
            contextMenu.style.display = 'none';  
            onClick();  
        };  
        return item;  
    }  

    // Attach context menu to each cell  
    table.querySelectorAll('tbody td').forEach(td => {  
        td.addEventListener('contextmenu', (e) => {  
            e.preventDefault();  
            lastTarget = td;  

            // Clear existing items  
            contextMenu.innerHTML = '';  
            contextMenu.appendChild(createMenuItem('๐Ÿงพ Open in new tab', () => {  
                const link = lastTarget.querySelector('a');  
                if (link && link.href) {  
                    window.open(link.href, '_blank');  
                } else {  
                    alert('No link found in cell');  
                }  
            }));  
            contextMenu.appendChild(createMenuItem('๐Ÿ” Filter by this value', () => {  
                if (!lastTarget) return;  

                const cell = lastTarget;  
                const colIndex = Array.from(cell.parentElement.children).indexOf(cell);  
                const value = cell.innerText.trim();  
                const th = table.querySelector(`thead tr`).children[colIndex];  
                const deselector = th.querySelector(`.NOH_selector`);  
                if (deselector && deselector.textContent === 'Deselect All') {  
                    deselector.click();  
                }  
                const checkbox = th.querySelector(`input[type="checkbox"][value="${value.replace(/"/g, '\\"')}"]`);  
                if (checkbox) {  
                    checkbox.checked = true;  
                    checkbox.dispatchEvent(new Event('change', { bubbles: true }));  
                }  
            }));  
            contextMenu.style.left = `${e.pageX}px`;  
            contextMenu.style.top = `${e.pageY}px`;  
            contextMenu.style.display = 'block';  
        });  
    });  

    // Hide menu when clicking elsewhere  
    document.addEventListener('click', () => {  
        contextMenu.style.display = 'none';  
    });  
}  


function filterTable(table) {  
    const tbody = table.querySelector('tbody');  
    const rows = Array.from(tbody.querySelectorAll('tr'));  
    const ths = Array.from(table.querySelectorAll('thead th'));  

    const filters = ths.map(th => {  
        const checkboxes = th.querySelectorAll('input[type=checkbox]');  
        const set = new Set(  
            Array.from(checkboxes)  
            .filter(cb => cb.checked)  
            .map(cb => cb.value)  
        );  
        return set;  
    });  
    rows.forEach(row => {  
        const cells = Array.from(row.children);  
        if (cells.length !== filters.length) {  
            row.style.display = 'none';  
            return;  
        }  
        const visible = filters.every((set, i) =>  
          set.size === ths[i].querySelectorAll('input[type=checkbox]').length ||  
          set.has(cells[i].innerText.trim())  
        );  
        row.style.display = visible ? '' : 'none';  
    });  

    // Update greyed-out state  
    ths.forEach((th, col) => {  
        const checkboxes = th.querySelectorAll('input[type=checkbox]');  
        checkboxes.forEach(cb => {  
            let stillVisible = false;  
            rows.forEach(row => {  
                if (row.style.display !== 'none' && row.children[col].innerText.trim() === cb.value) {  
                    stillVisible = true;  
                }  
            });  
            //cb.disabled = !stillVisible;  
            cb.parentElement.style.color = stillVisible ? '' : '#ccc';  
        });  
    });  
}
0