Sortable Table
Click any column header to sort. Click again to reverse.
Add data-numeric to headers whose columns contain numbers.
Live Demo
| Name | Department | Salary | Start Date |
|---|---|---|---|
| Alice Chen | Engineering | $95,000 | 2022-03-14 |
| Bob Martinez | Design | $82,000 | 2021-07-01 |
| Carol Singh | Marketing | $74,000 | 2023-01-09 |
| David Kim | Engineering | $105,000 | 2020-11-22 |
| Eve Johnson | Operations | $68,000 | 2023-06-30 |
| Frank Okonkwo | Design | $88,000 | 2022-09-05 |
Template Code
Copy templates/sortable_table.stpl into your app.stpl.
Replace the sample rows list with your own DB query.
Move the <style> block into your stylesheet.
{#
================================================================
Sortable Table (ScribeFramework template prefab)
How to use:
1. Copy this route into your app.stpl
2. Move the <style> block into your stylesheet (or keep it inline)
3. Replace the sample `rows` list with your own DB query
4. Update column headers and <td> cells to match your data
5. Add data-numeric to any <th> whose column contains numbers
No external dependencies — vanilla JS only.
================================================================
#}
@route('/example-table')
@no_layout
{$
page_title = "Sortable Table"
# Replace with your own database query, e.g.:
# rows = db['default'].query("SELECT name, department, salary, start_date FROM employees")
rows = [
{'name': 'Alice Chen', 'department': 'Engineering', 'salary': 95000, 'start_date': '2022-03-14'},
{'name': 'Bob Martinez', 'department': 'Design', 'salary': 82000, 'start_date': '2021-07-01'},
{'name': 'Carol Singh', 'department': 'Marketing', 'salary': 74000, 'start_date': '2023-01-09'},
{'name': 'David Kim', 'department': 'Engineering', 'salary': 105000, 'start_date': '2020-11-22'},
{'name': 'Eve Johnson', 'department': 'Operations', 'salary': 68000, 'start_date': '2023-06-30'},
{'name': 'Frank Okonkwo', 'department': 'Design', 'salary': 88000, 'start_date': '2022-09-05'},
]
$}
<style>
/* ── Sortable Table ── move this block to your stylesheet ───── */
.sort-table {
width: 100%;
border-collapse: collapse;
}
.sort-table th,
.sort-table td {
padding: 0.75rem 1rem;
text-align: left;
border-bottom: 1px solid var(--border-color, #e2e8f0);
}
.sort-table thead th {
background: var(--bg-color, #f8fafc);
font-weight: 600;
white-space: nowrap;
cursor: pointer;
user-select: none;
}
.sort-table thead th:hover {
color: var(--primary-color, #2563eb);
}
.sort-table tbody tr:hover {
background: var(--primary-light, #dbeafe);
}
.sort-icon {
display: inline-block;
margin-left: 0.25rem;
color: var(--text-muted, #64748b);
font-size: 0.8em;
}
th.sort-asc .sort-icon,
th.sort-desc .sort-icon {
color: var(--primary-color, #2563eb);
}
/* ──────────────────────────────────────────────────────────── */
</style>
<div class="container">
<h1>Employees</h1>
<div class="card" style="overflow-x: auto; padding: 0;">
<table id="employee-table" class="sort-table">
<thead>
<tr>
<th data-col="0">Name <span class="sort-icon">↕</span></th>
<th data-col="1">Department <span class="sort-icon">↕</span></th>
<th data-col="2" data-numeric>Salary <span class="sort-icon">↕</span></th>
<th data-col="3">Start Date <span class="sort-icon">↕</span></th>
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
<td>{{ row.name }}</td>
<td>{{ row.department }}</td>
<td>${{ "{:,}".format(row.salary) }}</td>
<td>{{ row.start_date }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<script>
(function () {
const table = document.getElementById('employee-table');
let sortCol = -1;
let sortAsc = true;
table.querySelectorAll('thead th').forEach(th => {
th.addEventListener('click', () => {
const col = parseInt(th.dataset.col);
const numeric = 'numeric' in th.dataset;
sortAsc = (sortCol === col) ? !sortAsc : true;
sortCol = col;
// Update header indicators
table.querySelectorAll('thead th').forEach(h => {
h.classList.remove('sort-asc', 'sort-desc');
h.querySelector('.sort-icon').textContent = '↕';
});
th.classList.add(sortAsc ? 'sort-asc' : 'sort-desc');
th.querySelector('.sort-icon').textContent = sortAsc ? '↑' : '↓';
// Sort rows
const tbody = table.querySelector('tbody');
Array.from(tbody.querySelectorAll('tr'))
.sort((a, b) => {
const av = a.cells[col].textContent.trim();
const bv = b.cells[col].textContent.trim();
const cmp = numeric
? parseFloat(av.replace(/[^0-9.]/g, '')) - parseFloat(bv.replace(/[^0-9.]/g, ''))
: av.localeCompare(bv);
return sortAsc ? cmp : -cmp;
})
.forEach(row => tbody.appendChild(row));
});
});
})();
</script>