ilyachalov (ilyachalov) wrote,
ilyachalov
ilyachalov

Categories:

JavaScript: сортировка строк в HTML-таблице

Решил задачу «Сортировка таблицы» к подразделу 1.7 «Изменение документа» второй части учебника по JavaScript.

Тестовая HTML-таблица уже имеется на нашей HTML-странице (имена и фамилии я перевел с английского на русский язык):
<table id="table">
  <tbody>
    <tr>                     <!-- первая (заголовочная) строка -->
      <th>Имя</th>
      <th>Фамилия</th>
      <th>Возраст</th>
    </tr>
    <tr>                     <!-- вторая строка -->
      <td>Джон</td>
      <td>Смит</td>
      <td>10</td>
    </tr>
    <tr>                     <!-- третья строка -->
      <td>Пётр</td>
      <td>Браун</td>
      <td>15</td>
    </tr>
    <tr>                     <!-- четвертая строка -->
      <td>Анна</td>
      <td>Ли</td>
      <td>5</td>
    </tr>
  </tbody>
</table>
Хоть в этой таблице задано лишь три строки (если не считать строки-заголовка), в задаче предупреждают, что решение должно быть для таблицы с любым количеством строк.

Нужно отметить, что в текстовом исходнике HTML-страницы в песочнице для нашей задачи теги <tbody> и </tbody> не прописаны (поэтому я отметил их серебристым цветом). Но по правилам языка HTML они обязательно должны быть (секция tbody в таблице по правилам HTML может быть одна или их может быть несколько), поэтому браузер (у меня — «Microsoft Edge» на базе движка «Chromium») добавляет их сам. В этом можно убедиться, запустив для загруженной HTML-страницы с данной HTML-таблицей инструмент «Elements» из инструментов разработчика (F12).

Выглядеть эта HTML-таблица в браузере может по-разному, в зависимости от стилизации с помощью языка CSS. Например, на отдельной странице обсуждаемой задачи в обсуждаемом учебнике эта HTML-таблица показана почти что без стилизации и выглядит некрасиво, примерно так:

ИмяФамилияВозраст
ДжонСмит10
ПётрБраун15
АннаЛи5

А на странице раздела 1.7 «Изменение документа» обсуждаемого учебника, в том месте, где описывается эта же задача, данная HTML-таблица выглядит гораздо лучше, примерно так:

ИмяФамилияВозраст
ДжонСмит10
ПётрБраун15
АннаЛи5

В принципе, внешний вид тестовой HTML-таблицы не относится к сути задания. Однако, мне хотелось, чтобы тестовая HTML-таблица выглядела красиво. Поэтому я посмотрел стили обсуждаемого учебника и описал их (те из них, которые относятся к внешнему виду нашей тестовой HTML-таблицы) в заголовочной части нашей HTML-страницы:
<style>
  table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
    font-family: "Segoe UI";
  }
  tr:nth-child(2n) {
    background: #f9f9f9;
  }
  tr {
    border-bottom: 1px solid #ccc;
  }
  td, th {
    padding: 2px 1em 2px 5px;
  }
  th {
    text-align: left;
    border-bottom: 3px solid #ccc;
  }
</style>

Тут стоит отметить псевдокласс :nth-child (для меня он был в новинку). В данном описании стилей с помощью этого псевдокласса определяется цвет фона (#f9f9f9) каждой четной строки (тег tr) тестовой HTML-таблицы. При этом нужно иметь в виду, что нумерация строк начинается с единицы. То есть первой строкой является заголовочная строка нашей HTML-таблицы, она нечетная, поэтому ее фон имеет цвет по умолчанию (в данном случае — белый). Вторая строка — это строка, содержащая значения «Джон», «Смит» и «10», она чётная, поэтому ее фон — серого (#f9f9f9) цвета. И так далее. Тут подробнее:

https://developer.mozilla.org/ru/docs/Web/CSS/:nth-child

Итак, в заданной HTML-таблице по условиям задачи требуется отсортировать строки (то есть переставить их местами) по столбцу «Имя».

В первую очередь я решил выбрать все строки (HTML-элементы tr) заданной HTML-таблицы. Рука потянулась к методу querySelectorAll, описанному в подразделе 1.4 «Поиск: getElement*, querySelector*» второй части обсуждаемого учебника. Но тут я вспомнил, что для HTML-таблиц существуют более удобные способы работы с частями этих таблиц. Они были описаны в подразделе 1.3 «Навигация по DOM-элементам» второй части обсуждаемого учебника. Например, получить коллекцию строк HTML-таблицы можно с помощью свойства rows HTML-таблицы. При построении тестовой HTML-таблицы ей был дан идентификатор table, поэтому получить коллекцию строк этой таблицы можно с помощью выражения table.rows.

В учебнике было сказано, что получаемая таким образом коллекция HTML-элементов не является массивом (это псевдомассив). А это значит, что к коллекции HTML-элементов нельзя будет применить методы массивов. А мы, ведь, хотим отсортировать строки HTML-таблицы, следовательно, нам нужно отсортировать полученную коллекцию строк HTML-таблицы. Можно, конечно, самому написать метод сортировки для коллекции. Но зачем, если метод сортировки уже написан, хоть и для массива? Просто преобразуем нашу коллекцию строк HTML-таблицы в массив, а затем используем готовый метод сортировки массива. Пишем код:
1.
let arr = Array.from( table.rows ); // преобразуем коллекцию в массив
arr.sort();                         // используем готовый метод сортировки массива

В этом коде используется сортировка массива по умолчанию (для метода сортировки не задан параметр). В этом случае элементы массива приводятся к строке, а затем происходит их сравнение. В нашем случае элементы массива являются объектами, HTML-элементами tr. При преобразовании каждого из этих объектов в строку получится одна и та же строка — "[object HTMLTableRowElement]". Метод сортировки посчитает все элементы массива равными и сортировки не произойдет. Поэтому нам следует прописать функцию, определяющую порядок сортировки, и передать ее в метод сортировки массива первым параметром. Меняем код:
2.
let arr = Array.from( table.rows );
arr.sort( (a, b) => {
    let str  = a.cells[0].textContent;
    let str2 = b.cells[0].textContent;
    return str.localeCompare(str2);
} );

Здесь функция, определяющая порядок сортировки, не имеет имени и задана стрелочной функцией. У этой функции два входящих параметра — a и b, это два элемента массива, сравниваемых при сортировке массива, выполняемой методом sort. То есть переменные a и b являются в данном случае HTML-элементами tr.

По условиям задачи для сравнения строк нашей HTML-таблицы следует использовать значения столбца «Имя», а этот столбец состоит из первых ячеек (HTML-элементов td или th) каждой строки нашей HTML-таблицы. Коллекцию этих ячеек можно получить с помощью свойства cells строки HTML-таблицы, это было описано в уже упомянутом подразделе 1.3 «Навигация по DOM-элементам» второй части обсуждаемого учебника. Первая ячейка каждой строки нашей HTML-таблицы и есть cells[0], а ее текстовое содержимое — cells[0].textContent. Таким образом в переменных str и str2 мы получаем текстовые строки, каждая из которых представляет свой объект [object HTMLTableRowElement], эти переменные мы и будем использовать для сравнения с помощью метода localeCompare.

Наш код уже отсортировал строки тестовой HTML-таблицы в массиве. Осталось лишь вставить их из массива обратно в тестовую HTML-таблицу. Сделаем это с помощью уже ставшего привычным (я его применял ранее для решения нескольких задач) метода append. В предыдущих постах я его применял для вставки одного HTML-элемента за раз, но, ведь, им можно вставлять сразу несколько HTML-элементов с помощью оператора расширения (многоточие). Меняем код:
3.
let arr = Array.from( table.rows );
arr.sort( (a, b) => {
    let str  = a.cells[0].textContent;
    let str2 = b.cells[0].textContent;
    return str.localeCompare(str2);
} );
table.append(...arr);

Может возникнуть вопрос: если мы только вставляем новые строки в тестовую HTML-таблицу и не удаляем старые, то всего строк теперь должно стать не четыре, а восемь (каждая исходная строка дублируется)? На самом деле, нет, потому что метод append (как и его коллеги) сначала удаляет HTML-элемент со старого места, а затем вставляет его на новое место. То есть фактически вставляются не новые элементы, а исходные.

Наш код отсортировал строки заданной HTML-таблицы и результат отобразился в браузере. Однако, вместе со всеми отсортировалась и заголовочная строка, которая, по идее, должна остаться на своем месте, вверху HTML-таблицы. Я решил отследить эту строку внутри функции, определяющей порядок сортировки, и сделать так, что она не будет двигаться со своего места. Меняем код:
4.
let arr = Array.from( table.rows );
arr.sort( (a, b) => {
    if (a.cells[0].tagName == "TH" ||
        b.cells[0].tagName == "TH") return 0;

    let str  = a.cells[0].textContent;
    let str2 = b.cells[0].textContent;
    return str.localeCompare(str2);
} );
table.append(...arr);

Это сработало, теперь заголовочная строка остаётся на своем месте. Однако, авторы задачи в своем решении предложили более изящный способ:
5.
let arr = Array.from( table.rows );
arr = arr.slice(1);
arr.sort( (a, b) => {
    let str  = a.cells[0].textContent;
    let str2 = b.cells[0].textContent;
    return str.localeCompare(str2);
} );
table.append(...arr);
Метод slice возвращает новый массив, в данном случае — без первого элемента (который и является заголовочной строкой нашей HTML-таблицы), и полученный массив записывается на место старого, в переменную arr. Таким образом, заголовочная строка вообще не принимает участия в сортировке и не перемещается на новое место (не попадает в руки метода append).

Теперь, казалось бы, всё в порядке. Но нет. Если просмотреть полученную HTML-страницу с помощью инструмента «Elements» из инструментов разработчика (F12) браузера, то можно увидеть, что метод append вставил HTML-элементы tr не внутрь HTML-элемента tbody (как положено по правилам языка HTML), а после него.

В некоторых случаях такое нарушение может повлиять на внешний вид страницы. Например, в случае кода из варианта 4 нашего скрипта (см. выше) собьется порядок окраски фона строк нашей тестовой HTML-таблицы: серым цветом будет окрашен фон нечетных строк (первой и третьей) вместо указанных в стиле четных (второй и четвертой). Это произойдет из-за того, что HTML-элемент tbody станет соседом HTML-элементов tr и при расчете номеров HTML-элементов в этой группе HTML-элементов он будет считаться первым (так как является первым ребенком общего для группы родителя). Таким образом, нумерация собьется и первая строка таблицы будет теперь считаться второй, вторая строка — третьей и так далее.

Чтобы поместить отсортированные строки нашей HTML-таблицы из массива обратно в HTML-элемент tbody, изменим последнюю строчку нашего кода:
6.
let arr = Array.from( table.rows );
arr = arr.slice(1);
arr.sort( (a, b) => {
    let str  = a.cells[0].textContent;
    let str2 = b.cells[0].textContent;
    return str.localeCompare(str2);
} );
table.tBodies[0].append(...arr);

Это финальный вариант скрипта. О свойстве tBodies рассказывалось в том же подразделе 1.3 «Навигация по DOM-элементам» второй части обсуждаемого учебника. Секций tbody, как уже упоминалось, в HTML-таблице может быть несколько. В нашем случае такая секция одна.
Tags: Образование, Программирование
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments