Estou tentando ordenar os saldos na tabela. Mas havia um problema. Eu estava tentando classificar com código:
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);
}
}
isso me foi sugerido em outra pergunta. Mas o console javascript no navegador me mostrou o erro:
Uncaught TypeError: Cannot read properties of undefined (reading 'textContent')
O estágio em que esse erro me mostra:
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);
}
Presumo que js reclame de uma célula vazia em uma coluna, pois removo todas as linhas desnecessárias usando slice depois de exibir o textContent das células dessas mesmas linhas. Mas ainda não entendo como consertar isso.
Meu tableRender com 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);
Meu 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.