table = (data, options) => {
options = Object.assign({}, defaultOptions, options);
const { sortable, rank, paged } = options;
let sortKey = undefined;
let sortDirection = true;
let page = 0;
if (sortable && rank) {
throw new Error("A table can either be ranked or sortable, but not both");
}
let columns = Object.keys(data[0]).map(key => {
const opts = options.columns[key] || {};
return {
key: key,
type: opts.type || typeof data[0][key],
options: opts
};
});
function bake() {
if (sortKey) {
data = data.slice().sort((a, b) => {
let as = a[sortKey];
let bs = b[sortKey];
if (as == bs) return JSON.stringify(a).localeCompare(JSON.stringify(b));
let res = as > bs ? 1 : as < bs ? -1 : 0;
if (sortDirection) res = -res;
return res;
});
}
let offset = page * paged;
let rows = data.slice(offset, offset + (paged || data.length));
let pages = paged ? Math.ceil(data.length / paged) : 1;
return html`<div><div>
<style>
.pretty-pager {
padding-top: 1rem;
}
.pretty-pager button {
cursor: pointer;
border-radius: 3px;
border: 1px solid #fff;
font-size: inherit;
}
.pretty-pager button:hover {
border: 1px solid #888;
}
.pretty-table.normal {
font-size: 15px;
}
.pretty-table.normal th,
.pretty-table.normal td {
padding: 3px 2px;
}
.pretty-table th,
.pretty-table td {
vertical-align: top;
}
.pretty-table thead th {
text-transform: uppercase;
font-weight:500;
}
.pretty-table thead th.column-type-number string {
order: 1;
}
.pretty-table th.sortable {
cursor: pointer;
}
.pretty-table thead th.column-type-number,
.pretty-table tbody td.cell-type-number,
.pretty-table tbody td.cell-rank {
text-align:right;
}
.pretty-table tbody td.cell-type-number,
.pretty-table tbody td.cell-rank {
font-family: menlo,consolas,monaco,monospace;
font-size: 90%;
}
.pretty-table tbody td.cell-rank {
padding-right: 1em;
color: #666;
}
</style>
<table class='pretty-table ${options.style}'>
${
options.header === false
? ``
: html`<thead>
${rank ? html`<th></th>` : ""}
${columns.map(c => {
return th(c, sortKey, sortDirection, sortable);
})}
</thead>`
}
<tbody>
${rows.map(
(row, i) => html`<tr>
${rank ? html`<td class='cell-rank'>${offset + i + 1}</td>` : ""}
${columns.map(c => {
let displayValue = (c.options.formatter || identity)(
row[c.key],
i,
row
);
if (
displayValue instanceof window.HTMLElement &&
displayValue.tagName == "TD"
) {
return displayValue;
}
return html`<td class='cell-type-${c.type}'>${displayValue}</td>`;
})}
</tr>`
)}
</tbody>
</table>
${
pages > 1
? html`<div class='pretty-pager'>
<button data-action="previous">Previous</button>
${Array.from({ length: pages }).map(
(_, i) => html`<button data-page="${i}">${i + 1}</button>`
)}
<button data-action="next">Next</button>
</div>`
: ""
}
</div></div>`;
}
let dom = bake();
function rerender() {
dom.firstChild.remove();
dom.appendChild(bake().firstChild);
}
dom.addEventListener("click", e => {
if (e.target.tagName === "TH" && sortable) {
if (sortKey == e.target.dataset.key) {
sortDirection = !sortDirection;
}
sortKey = e.target.dataset.key;
rerender();
}
if (e.target.tagName === "BUTTON") {
if (e.target.dataset.action) {
switch (e.target.dataset.action) {
case "next":
page++, rerender();
break;
case "previous":
page--, rerender();
break;
}
} else if (e.target.dataset.page) {
(page = parseInt(e.target.dataset.page)), rerender();
}
}
});
return dom;
}