gnat / surreal

🗿 Mini jQuery alternative. Dependency-free animations. Locality of Behavior. Use one element or arrays transparently. Pairs with htmx. Vanilla querySelector() but better!
https://gnat.github.io/surreal/example.html
MIT License
1.2k stars 24 forks source link

Surreal errors when rerunning function after HTMX adds row to table. #7

Closed figuerom16 closed 11 months ago

figuerom16 commented 11 months ago

I'm currently trying to use Surreal and HTMX to create a simple datatable. The datatable works fine, but when a new row is added I want to rerun the surreal function so that the new row will sort and filter as well.

The two things I've tried is to do so far is:

Both result in a Uncaught TypeError: start.currentScript is null

Here is the console error:

Uncaught TypeError: start.currentScript is null
    me http://127.0.0.1:3000/js/surreal.js:56
    <anonymous> http://127.0.0.1:3000/test:100
    value http://127.0.0.1:3000/js/htmx.js:473
    triggerEvent http://127.0.0.1:3000/js/htmx.js:2145
    <anonymous> http://127.0.0.1:3000/js/htmx.js:3781
    setTimeout handler* http://127.0.0.1:3000/js/htmx.js:3780
    ready http://127.0.0.1:3000/js/htmx.js:3716
    <anonymous> http://127.0.0.1:3000/js/htmx.js:3749
    <anonymous> http://127.0.0.1:3000/js/htmx.js:3788
    <anonymous> http://127.0.0.1:3000/js/htmx.js:16
    <anonymous> http://127.0.0.1:3000/js/htmx.js:18
surreal.js:56:25
    me http://127.0.0.1:3000/js/surreal.js:56
    <anonymous> http://127.0.0.1:3000/test:100
    value http://127.0.0.1:3000/js/htmx.js:473
    triggerEvent http://127.0.0.1:3000/js/htmx.js:2145
    <anonymous> http://127.0.0.1:3000/js/htmx.js:3781
    (Async: setTimeout handler)
    <anonymous> http://127.0.0.1:3000/js/htmx.js:3780
    (Async: EventListener.handleEvent)
    ready http://127.0.0.1:3000/js/htmx.js:3716
    <anonymous> http://127.0.0.1:3000/js/htmx.js:3749
    <anonymous> http://127.0.0.1:3000/js/htmx.js:3788
    <anonymous> http://127.0.0.1:3000/js/htmx.js:16
    <anonymous> http://127.0.0.1:3000/js/htmx.js:18

Here is a working example of the datatable minus the button hx-get="/rows". Though one could simply copy and paste a new row in the inspector.

TEST.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <script src="https://unpkg.com/htmx.org@1.9.6"></script>
    <script src="https://cdn.jsdelivr.net/gh/gnat/surreal/surreal.js"></script>
    <style>
        table {
            width:100%;
        }
        table, th, td {
            border: 1px solid black;
            border-collapse: collapse;
        }
        input {
            margin: 1rem;
        }
    </style>
</head>
<body>

<h2>Table</h2>

<button hx-get="/row" hx-target="table" hx-swap="beforeend">ADD ROW</button>

<table>
    SEARCH: <input type="text" />
    <tr>
        <th>Firstname</th>
        <th>Lastname</th>
        <th>Age</th>
    </tr>
    <tr>
        <td>Jill</td>
        <td>Smith</td>
        <td>50</td>
    </tr>
    <tr>
        <td>Eve</td>
        <td>Jackson</td>
        <td>94</td>
    </tr>
    <tr>
        <td>Dork</td>
        <td>Lee</td>
        <td>32</td>
    </tr>
    <tr>
        <td>Fork</td>
        <td>Bee</td>
        <td>69</td>
    </tr>
    <script>
        (_=>{
            // SETUP
            let table = me()
            let rows = Array.from(table.rows)
            let header = rows.shift()
            let search = me('input')

            // SEARCH
            search.on('keyup', _=>{
                const term = search.value.toLowerCase()
                rows.forEach((row,i)=>{ 
                    const found = Array.from(row.cells).some(cell=>cell.innerText.toLowerCase().includes(term))
                    row.style.display = found ? '' : 'none'
                })
            })

            // SORT
            Array.from(header.cells).forEach((cell,i)=>{
                cell.innerText += ' ►'
                cell.addEventListener('click', _=>{
                    const arrow = cell.innerText.substr(-1)
                    Array.from(header.cells).forEach(cell=>cell.innerText = cell.innerText.slice(0, -1) + '►')
                    if (arrow === '▼') {
                        rows.sort((a,b)=>b.cells[i].innerText.localeCompare(a.cells[i].innerText))
                        cell.innerText = cell.innerText.slice(0, -1) + '▲'
                    }
                    else {
                        rows.sort((a,b)=>a.cells[i].innerText.localeCompare(b.cells[i].innerText))
                        cell.innerText = cell.innerText.slice(0, -1) + '▼'
                    }
                    rows.forEach(row=>table.appendChild(row))
                })
            })
        })()
    </script>
</table>

</body>
</html>

My question would be what is the proper way to rerun a surreal function? Also thank you for the library! It's been super convenient!

gazpachoking commented 11 months ago

I think you just need to take your code out of that function. me() only works when top level inside a <script> block. Also, while me('input') will work in your case, be aware that it is no longer scoped to just be inside your <table>, which might be an issue if your page grows.

figuerom16 commented 11 months ago

I had a feeling that was the case. I'll switch back to plain JS for this piece as I was planning on stuffing surreal into the individual elements instead for editing which honestly makes more sense and less messy.

Thank you for your time.

figuerom16 commented 11 months ago

I did finally figure out how to get it working the surreal way by using the table itself as the source of truth and properly using me(). This will work nicely with templates and allow me to easily expand it functionality.

Thanks again for the help!

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8" />
<script src="https://unpkg.com/htmx.org@1.9.6"></script>
<script src="https://cdn.jsdelivr.net/gh/gnat/surreal/surreal.js"></script>
<script>
function sort_table(rows, column, arrow) {
    let switching = true, m = 0
    if (arrow === '▼') m = 1
    while (switching) {
        switching = false
        for (let i = 1; i < (rows.length - 1); i++) {
            if (rows[i + m].getElementsByTagName("td")[column].innerHTML
            .localeCompare(rows[i + 1 - m].getElementsByTagName("td")[column].innerHTML, undefined, { numeric: true }) > 0) {
                rows[i].parentNode.insertBefore(rows[i + 1], rows[i])
                switching = true
                break
            }
        }
    }
}
</script>
<style>
table {
    width:100%;
}
table, th, td {
    border: 1px solid black;
    border-collapse: collapse;
}
input {
    margin: 1rem;
}
</style>
</head>
<body>

<h2>Table</h2>

<button hx-get="/row" hx-target="#test" hx-swap="beforeend">ADD ROW</button>

SEARCH: <input type="text" />
<script>
    me().on('keyup', ev=>{
        let rows = Array.from(me('#test').rows)
        rows.shift()
        const term = me(ev).value.toLowerCase()
        rows.forEach((row,i)=>{ 
            const found = Array.from(row.cells).some(cell=>cell.innerText.toLowerCase().includes(term))
            row.style.display = found ? '' : 'none'
        })
    })
</script>
<table id="test">
    <tr>
        <th>Firstname ►
            <script>
                me().on('click', ev=>{
                    const arrow = me(ev).innerText.substr(-1)
                    any('#test th').run(x=>{x.innerText=x.innerText.slice(0, -1) + '►'})
                    if (arrow === '▼') me(ev).innerText = me(ev).innerText.slice(0, -1) + '▲'
                    else me(ev).innerText = me(ev).innerText.slice(0, -1) + '▼'
                    sort_table(me("#test").rows, 0, arrow)
                })
            </script>
        </th>
        <th>Lastname ►
            <script>
                me().on('click', ev=>{
                    const arrow = me(ev).innerText.substr(-1)
                    any('#test th').run(x=>{x.innerText=x.innerText.slice(0, -1) + '►'})
                    if (arrow === '▼') me(ev).innerText = me(ev).innerText.slice(0, -1) + '▲'
                    else me(ev).innerText = me(ev).innerText.slice(0, -1) + '▼'
                    sort_table(me("#test").rows, 1, arrow)
                })
            </script>
        </th>
        <th>Age ►
            <script>
                me().on('click', ev=>{
                    const arrow = me(ev).innerText.substr(-1)
                    any('#test th').run(x=>{x.innerText=x.innerText.slice(0, -1) + '►'})
                    if (arrow === '▼') me(ev).innerText = me(ev).innerText.slice(0, -1) + '▲'
                    else me(ev).innerText = me(ev).innerText.slice(0, -1) + '▼'
                    sort_table(me("#test").rows, 2, arrow)
                })
            </script>
        </th>
    </tr>
    <tr>
        <td>Jill</td>
        <td>Smith</td>
        <td>50</td>
    </tr>
    <tr>
        <td>Eve</td>
        <td>Jackson</td>
        <td>94</td>
    </tr>
    <tr>
        <td>Dork</td>
        <td>Lee</td>
        <td>32</td>
    </tr>
    <tr>
        <td>Fork</td>
        <td>Bee</td>
        <td>69</td>
    </tr>
</table>
</body>
</html>