Я пытаюсь отсортировать остатки в таблице. Но возникла проблема. Я пытался сортировать с помощью кода:
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>;
🤔 А знаете ли вы, что...
JavaScript является одним из трех основных языков веб-разработки, вместе с HTML и CSS.
Проблема возникает из-за того, что в строке нижнего колонтитула нет третьей ячейки. У него есть объединенная ячейка, охватывающая всю строку. Итак, код, который обращается к cell[2].textContent
, выдает ошибку, потому что undefined
не имеет свойства textContent
. Вызов slice
выглядит как попытка избежать этого, но дело доходит до опоздания: ошибка возникает до того, как slice
выполнится.
Один из способов решить эту проблему — выбирать только те строки tr
, которые являются дочерними по отношению к tbody
. Тогда не стоит нарезать. Добавьте сортировку и всё заработает:
function sortTableByBalance() {
const table = document.getElementById("myTable");
// Only select rows that are not in THEAD or TFOOT:
const tableRows = table.querySelectorAll("tr:not(thead>tr,tfoot>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
. Если вы хотите, чтобы отсортированные строки появлялись в этом единственном элементе tbody
, обязательно обновите приведенный выше код в этом последнем цикле, чтобы он выполнял appendChild
для этого элемента tbody
, а не для table
.
Код добавляет thead
на каждой итерации цикла. Только в первый раз это даст заметный эффект. Было бы разумнее поместить это перед циклом, чтобы оно выполнялось только один раз, а также в том случае, если таблица окажется пустой.
Аналогично, добавление таблицы к фрагменту не должно происходить в цикле.
Вместо применения стилей непосредственно к элементам td
рассмотрите возможность применения классов CSS. Это лучшая практика.
Поскольку у вас есть довольно много повторений кода для создания элемента, а затем установки его атрибутов HTML и текстового содержимого, имело бы смысл создать функцию для выполнения этих действий над одним данным элементом: параметрами будут этот элемент, список атрибутов и текст. содержание. Вы даже можете иметь необязательный аргумент для дочерних элементов, чтобы можно было создать целую иерархию элементов в одном выражении. Или используйте библиотеку, например jQuery, которая предоставляет эти функции.