我试图对表中的余额进行排序。但是有一个问题。我尝试用代码排序:
function sortTableByBalance() {
const table = document.getElementById("myTable");
const arr = Array.from(table.rows, row => [row, +row.cells[2].textContent]).slice(1);
arr.sort(([,a], [,b]) => a - b);
for (const [elem] of arr) {
table.appendChild(elem);
}
}
这是在另一个问题中向我建议的。但浏览器中的 JavaScript 控制台向我显示了错误:
Uncaught TypeError: Cannot read properties of undefined (reading 'textContent')
这个错误向我展示的阶段:
const sortBtnBalance = document.querySelector(".sort-balance-btn");
sortBtnBalance.addEventListener("click", sortTableByBalance);
function sortTableByBalance() {
const table = document.getElementById("myTable");
const tableRows = table.rows;
const arrs = Array.from(tableRows, (row) => [
row,
+row.cells[3].textContent,
]).slice(1, -1);
}
我假设 js 抱怨列中存在空单元格,因为在显示这些相同行的单元格的 textContent 后,我使用切片删除了所有不必要的行。但我还是不明白如何解决这个问题。
我的表用 javascript 渲染:
// дать нам новый массив объектов
const usrs = getUsers();
// основной контейнер для таблицы
const usersContainer = document.querySelector(
".users-list-section .users-group"
);
// статическая thead таблицы
function theadTemplate() {
// создать элемент thead
const thead = document.createElement("thead");
// создать элемент tr
const tr = document.createElement("tr");
// создать элемент th1 и дать ему атрибуты
const th1 = document.createElement("th");
th1.setAttribute("scope", "col");
th1.textContent = "#";
/* reset sort btn */
const resetSortBtn = document.createElement("button");
resetSortBtn.setAttribute("type", "button");
resetSortBtn.addEventListener("click", renderUsers);
resetSortBtn.classList.add("btn", "btn-dark", "reset-sort-btn");
const i0 = document.createElement("i");
i0.classList.add("bi", "bi-arrow-clockwise", "reset-clockwise");
resetSortBtn.appendChild(i0);
th1.appendChild(resetSortBtn);
// создать элемент th2 и дать ему атрибуты
const th2 = document.createElement("th");
th2.setAttribute("scope", "col");
th2.textContent = "Name";
// sort btn для сортировки юзеров по именам
const sortBtnNames = document.createElement("button");
sortBtnNames.setAttribute("type", "button");
sortBtnNames.setAttribute("onclick", "namesSortHandler(1)");
sortBtnNames.classList.add("btn", "btn-dark", "sort-names-btn");
// иконка стрелка
const i1 = document.createElement("i");
i1.classList.add("bi", "bi-arrow-up", "sort-names-arrow");
// добавить стрелку в кнопку и кнопку в th
sortBtnNames.appendChild(i1);
th2.appendChild(sortBtnNames);
// создать элемент th3 и дать ему атрибуты
const th3 = document.createElement("th");
th3.setAttribute("scope", "col");
th3.textContent = "Email";
// создать элемент th4 и дать ему атрибуты
const th4 = document.createElement("th");
th4.setAttribute("scope", "col");
th4.textContent = "Balance";
// sort btn для сортировки юзеров по балансу
const sortBtnBalance = document.createElement("button");
sortBtnBalance.setAttribute("type", "button");
sortBtnBalance.classList.add("btn", "btn-dark", "sort-balance-btn");
// иконка стрелка
const i2 = document.createElement("i");
i2.classList.add("bi", "bi-arrow-up", "sort-balance-arrow");
// добавить стрелку в кнопку и кнопку в th
sortBtnBalance.appendChild(i2);
th4.appendChild(sortBtnBalance);
// добавить детей в родителей
tr.appendChild(th1);
tr.appendChild(th2);
tr.appendChild(th3);
tr.appendChild(th4);
thead.appendChild(tr);
// вернуть полноценно собранный thead
return thead;
}
// полноценно собранный thead из функции записать в переменную.
const thead = theadTemplate();
// tbody таблицы (основное тело, куда будут писаться пользователи.)
function tbodyTemplate({
_id,
name,
email,
isActive,
balance,
number,
wiki,
} = {}) {
// создать элемент tbody
const tbody = document.createElement("tbody");
// создать элемент tr
const tr = document.createElement("tr");
// создать элемент th и дать ему классы и стили
const th = document.createElement("th");
th.classList.add("table-secondary");
th.style.color = "black";
th.style.fontWeight = "bold";
th.setAttribute("scope", "row");
th.textContent = number;
// создать элемент tdName и дать ему классы и стили
const tdName = document.createElement("td");
tdName.classList.add("table-secondary");
// nameLink имя с ссылкой на статью в wiki каждого юзера
const nameLink = document.createElement("a");
nameLink.setAttribute("href", wiki);
nameLink.setAttribute("target", "_blank");
nameLink.style.color = "black";
nameLink.style.fontWeight = "bold";
nameLink.textContent = name;
// создать элемент tdEmail и дать ему классы и стили
const tdEmail = document.createElement("td");
tdEmail.classList.add("table-secondary");
tdEmail.style.color = "black";
tdEmail.style.fontWeight = "bold";
tdEmail.textContent = email;
// создать элемент tdBalance и дать ему классы и стили
const tdBalance = document.createElement("td");
tdBalance.classList.add("table-secondary", "tdbalance");
tdBalance.style.color = "black";
tdBalance.style.fontWeight = "bold";
tdBalance.textContent = Number(balance);
// Добавляем детей в родителей, собираем конструкцию
tr.appendChild(th);
tdName.appendChild(nameLink);
tr.appendChild(tdName);
tr.appendChild(tdEmail);
tr.appendChild(tdBalance);
tbody.appendChild(tr);
// вернуть собранный tbody
return tbody;
}
// Каркас итогового общего баланса юзеров
function totalBalanceTemplate(balance) {
// В balance мы передаём массив балансов всех юзеров из функции renderUsers
// Просуммировать балансы всех юзеров
const result = balance.reduce((acc, curr) => {
let result;
acc = acc + curr;
result = acc;
return result;
}, 0);
// элемент футера для таблицы
const tfoot = document.createElement("tfoot");
// контейнер для строки таблицы
const tr = document.createElement("tr");
// сама строка таблицы, здесь примечателен аттрибут colspan
// в текст контент записываем результат суммирования балансов юзеров с двумя знаками после запятой
const td = document.createElement("td");
td.setAttribute("colspan", "4");
td.style.textAlign = "end";
td.style.fontWeight = "bold";
td.textContent = `Total balance: ${result.toFixed(2)}`;
tr.appendChild(td);
tfoot.appendChild(tr);
// вернуть собранный tfoot
return tfoot;
}
// table таблица и render users отображение в ней пользователей
function renderUsers(usersList) {
// создать фрагмент
const fragment = document.createDocumentFragment();
// создать элемент таблицы и дать ей классы bootstrap
const table = document.createElement("table", "users-table");
table.setAttribute("id", "myTable");
table.classList.add("table-dark", "table", "table-hover", "col");
// важно создать тотал баланс именно вне foreach и записать его в таблицу так же вне
// иначе тотал баланс у нас будет висеть после каждого пользователя
let totalBalance;
Object.values(usersList).forEach((user) => {
// создать массив из балансов всех юзеров, записать в result
let result = usersList.map((user) => Number(user.balance));
// записать в переменную totalBalance функцию возвращающую каркас(template)
// передать массив балансов в функцию totalBalanceTemplate
totalBalance = totalBalanceTemplate(result);
// здесь записываем тело таблицы в переменную, она возвращает каркас(template)
// сюда уже передаём просто юзеров
const tbody = tbodyTemplate(user);
// добавляем элементы в таблицу
table.appendChild(thead);
table.appendChild(tbody);
fragment.appendChild(table);
});
// последний элемент в таблицу, totalBalance
table.appendChild(totalBalance);
usersContainer.appendChild(fragment);
return table;
}
renderUsers(usrs);
我的html:
<div class="users-wrapper active">
<div class="users-list-section mt-5">
<div class="container">
<ul class="users-group">
<table
is="users-table"
id="myTable"
class="table-dark table table-hover col"
>
<tbody>
<tr>
<th
class="table-secondary"
scope="row"
style="color: black; font-weight: bold;"
>
1
</th>
<td class="table-secondary">
<a
href="https://en.wikipedia.org/wiki/H._P._Lovecraft"
target="_blank"
style="color: black; font-weight: bold;"
>
Govard Lovecraft
</a>
</td>
<td
class="table-secondary"
style="color: black; font-weight: bold;"
>
[email protected]
</td>
<td
class="table-secondary tdbalance"
style="color: black; font-weight: bold;"
>
2853.33
</td>
</tr>
</tbody>
<tbody>
<tr>
<th
class="table-secondary"
scope="row"
style="color: black; font-weight: bold;"
>
2
</th>
<td class="table-secondary">
<a
href="https://en.wikipedia.org/wiki/Yukio_Mishima"
target="_blank"
style="color: black; font-weight: bold;"
>
Yukio Mishima
</a>
</td>
<td
class="table-secondary"
style="color: black; font-weight: bold;"
>
[email protected]
</td>
<td
class="table-secondary tdbalance"
style="color: black; font-weight: bold;"
>
1464.63
</td>
</tr>
</tbody>
<tbody>
<tr>
<th
class="table-secondary"
scope="row"
style="color: black; font-weight: bold;"
>
3
</th>
<td class="table-secondary">
<a
href="https://en.wikipedia.org/wiki/Taras_Shevchenko"
target="_blank"
style="color: black; font-weight: bold;"
>
Taras Shevchenko
</a>
</td>
<td
class="table-secondary"
style="color: black; font-weight: bold;"
>
[email protected]
</td>
<td
class="table-secondary tdbalance"
style="color: black; font-weight: bold;"
>
3723.39
</td>
</tr>
</tbody>
<thead>
<tr>
<th scope="col">
#
<button type="button" class="btn btn-dark reset-sort-btn">
<i class="bi bi-arrow-clockwise reset-clockwise"></i>
</button>
</th>
<th scope="col">
Name
<button
type="button"
onclick="namesSortHandler(1)"
class="btn btn-dark sort-names-btn"
>
<i class="bi bi-arrow-up sort-names-arrow"></i>
</button>
</th>
<th scope="col">Email</th>
<th scope="col">
Balance
<button type="button" class="btn btn-dark sort-balance-btn">
<i class="bi bi-arrow-up sort-balance-arrow"></i>
</button>
</th>
</tr>
</thead>
<tbody>
<tr>
<th
class="table-secondary"
scope="row"
style="color: black; font-weight: bold;"
>
4
</th>
<td class="table-secondary">
<a
href="https://en.wikipedia.org/wiki/Dante_Alighieri"
target="_blank"
style="color: black; font-weight: bold;"
>
Dante Alighieri
</a>
</td>
<td
class="table-secondary"
style="color: black; font-weight: bold;"
>
[email protected]
</td>
<td
class="table-secondary tdbalance"
style="color: black; font-weight: bold;"
>
5000.21
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="4" style="text-align: end; font-weight: bold;">
Total balance: 13041.56
</td>
</tr>
</tfoot>
</table>
</ul>
</div>
</div>
</div>;
出现该问题的原因是页脚行中没有第三个单元格。它有一个跨越整行的合并单元格。因此,访问
cell[2].textContent
的代码会引发错误。
解决此问题的一种方法是仅选择
tr
子级的 tbody
行。那么你不应该切片。添加排序就可以了:
function sortTableByBalance() {
const table = document.getElementById("myTable");
// Only select rows in TBODY:
const tableRows = table.querySelectorAll("tbody>tr");
const arr = Array.from(tableRows, (row) => [row, +row.cells[3].textContent]);
// No slice... but sort:
arr.sort(([,a], [,b]) => a - b);
for (const [elem] of arr) {
table.appendChild(elem);
}
}
关于表格创建函数的一点注释:它每行创建一个
tbody
。虽然 HTML 中允许这样做,但这在您的情况下没有任何用处,并且一旦排序,所有数据行无论如何都会以单个 tbody
结束。我建议调整您的函数,只需从代码中删除 tbody
包装并返回 tr
并将其直接附加到您的 table
中。浏览器会默默添加单个 tbody
,所以不用担心。