我试图对表中的余额进行排序。但是有一个问题。我尝试用代码进行排序:
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 后,我使用切片删除了所有不必要的行。但我仍然不明白如何解决这个问题。
我的 tableRender 使用 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>;
The problem occurs because the footer row has no third cell in its row. It has a merged cell spanning the whole row. And so the code that accesses
cell[2].textContent
raises an error becauseundefined
has notextContent
property. Theslice
call looks like an attempt to avoid this, but it comes to late: the error occurs before thatslice
executes.One way to resolve this is to only select the
tr
rows that are child oftbody
. Then you shouldn't slice. Add the sorting and it will work:Other Remarks
With the above change it should work. But there are a few things that could be improved:
Your table creating function creates one
tbody
per row. Although this is allowed in HTML, this does not have any use in your case, and once you sort, all data rows will be moved out of their parenttbody
anyhow. I would suggest adjusting your function, and just remove thetbody
wrapping from your code and return thetr
instead and append that directly to yourtable
. Alternatively, you could work with exactly onetbody
element. If you want the sorted rows to appear in that singletbody
element, then make sure to update the above code in that final loop, so that it performsappendChild
to thattbody
element, not thetable
.The code adds the
thead
in every iteration of the loop. Only the first time it would have a detectable effect. It would make more sense to place this before the loop, so it is only executed once, and also if the table happens to be empty.Similarly, the addition to the table to the fragment should not be in the loop.
Instead of applying styles directly on your
td
elements, consider applying CSS classes. This is the better practice.As you have quite some code repetition to create an element and then set its HTML attributes and text content, it would make sense to make a function for performing these actions on one given element: parameters would be that element, the attribute list and the text content. You could even have an optional argument for child element(s), so that you can create a whole hierarchy of elements in one expression. Or use a library like jQuery, that provides these features.