// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {assert} from 'chrome://resources/js/assert.js';
import {CustomElement} from 'chrome://resources/js/custom_element.js';
import {getTemplate} from './private_aggregation_internals_table.html.js';
import type {TableModel} from './table_model.js';
/**
* Helper function for setting sort attributes on |th|.
*/
function setSortAttrs(th: HTMLElement, sortDesc: boolean|null) {
let nextDir;
if (sortDesc === null) {
th.ariaSort = 'none';
nextDir = 'ascending';
} else if (sortDesc) {
th.ariaSort = 'descending';
nextDir = 'ascending';
} else {
th.ariaSort = 'ascending';
nextDir = 'descending';
}
th.title = `Sort by ${th.innerText} ${nextDir}`;
th.ariaLabel = th.title;
}
/**
* Table abstracts the logic for rendering and sorting a table. The table's
* columns are supplied by a TableModel supplied to the decorate function. Each
* Column knows how to render the underlying value of the row type T, and
* optionally sort rows of type T by that value.
*/
export class PrivateAggregationInternalsTableElement<T> extends CustomElement {
static override get template() {
return getTemplate();
}
private model_: TableModel<T>|null = null;
private sortDesc_: boolean = false;
setModel(model: TableModel<T>) {
this.model_ = model;
this.sortDesc_ = false;
const tr = this.getRequiredElement('tr');
model.cols.forEach((col, idx) => {
const th = document.createElement('th');
th.scope = 'col';
col.renderHeader(th);
if (col.compare) {
th.setAttribute('role', 'button');
setSortAttrs(th, /*sortDesc=*/ null);
th.addEventListener('click', () => this.changeSortHeader_(idx));
}
tr.appendChild(th);
});
this.addEmptyStateRow_();
this.model_.rowsChangedListeners.add(() => this.updateTbody());
}
private addEmptyStateRow_() {
const td = document.createElement('td');
assert(this.model_);
td.textContent = this.model_.emptyRowText;
td.colSpan = this.model_.cols.length;
const tr = document.createElement('tr');
tr.appendChild(td);
const tbody = this.getRequiredElement('tbody');
tbody.appendChild(tr);
}
private changeSortHeader_(idx: number) {
const ths = this.$all<HTMLElement>('thead th');
assert(this.model_);
if (idx === this.model_.sortIdx) {
this.sortDesc_ = !this.sortDesc_;
} else {
this.sortDesc_ = false;
if (this.model_.sortIdx >= 0) {
setSortAttrs(ths[this.model_.sortIdx]!, /*descending=*/ null);
}
}
this.model_.sortIdx = idx;
setSortAttrs(ths[this.model_.sortIdx]!, this.sortDesc_);
this.updateTbody();
}
private sort_(rows: T[]) {
assert(this.model_);
if (this.model_.sortIdx < 0) {
return;
}
const multiplier = this.sortDesc_ ? -1 : 1;
rows.sort(
(a, b) => this.model_!.cols[this.model_!.sortIdx]!.compare!(a, b) *
multiplier);
}
updateTbody() {
const tbody = this.getRequiredElement('tbody');
tbody.innerText = '';
assert(this.model_);
const rows = this.model_.getRows();
if (rows.length === 0) {
this.addEmptyStateRow_();
return;
}
this.sort_(rows);
rows.forEach((row) => {
const tr = document.createElement('tr');
assert(this.model_);
this.model_.cols.forEach((col) => {
const td = document.createElement('td');
col.render(td, row);
tr.appendChild(td);
});
this.model_.styleRow(tr, row);
tbody.appendChild(tr);
});
}
}
customElements.define(
'aggregation-service-internals-table',
PrivateAggregationInternalsTableElement);