Как включить комбинированные фильтры с помощью JavaScript?

#javascript #html

#javascript #HTML

Вопрос:

Для справки, полный код можно получить здесь

У меня есть три элемента выбора, которые должны действовать как фильтры на моей странице, вызывая myfilter функцию. Затем в таблице я использую data- атрибут в некоторых <td> элементах, чтобы «классифицировать» их без необходимости устанавливать разные классы:

 <select id="formats" onchange="myfilter('formats', 'mytable')">
  <option value="nofilter">--ALL VALUES--</option>
  <option value="apple">Apples</option>
  <option value="orange">Oranges</option>
  <option value="noattr">Unknown</option>
</select>

<table>
  <tr>
    <td data-format="apple" style="background-color:red;width:10px">amp;nbsp;</td>
    <td data-model="film" style="background-color:#3ca971;width:10px">amp;nbsp;</td>
    <td data-type="water" style="background-color:#458985;width:10px">amp;nbsp;</td>
    <th>1</th>
    <td>Michael</td>
    <td>New York</td>
    <td>M</td>
  </tr>
</table>
 

myfilter Функция, адаптированная из примера из W3Schools, извлекает значение выбранного списка, определяет, какой столбец он должен искать, и сравнивает пользовательский data- атрибут со значением, чтобы сохранить строки видимыми или скрыть их:

 function myfilter(myelement, reftable) {
    var input, filter, table, tr, td, i, j, txtValue, rowstyle, checkvisible;
    // Obtains the "value" element of the selected listbox
    input = document.getElementById(myelement);
    filter = input.value;

    table = document.getElementById(reftable);
    tr = table.getElementsByTagName("tr");
    switch(myelement) {
        case "formats":
            j = 0;
            break;
        case "models":
            j = 1;
            break;
        case "types":
            j = 2;
    }
    for (i = 0; i < tr.length; i  ) {
        td = tr[i].getElementsByTagName("td")[j];
        if (td) {
            // Retrieves the respective "data-" attribute from <td>
            switch(myelement) {
                case "formats":
                    txtValue = td.dataset.format;
                    break;
                case "models":
                    txtValue = td.dataset.model;
                    break;
                case "types":
                    txtValue = td.dataset.type;
            }
            // If there's no style attribute, it's visibleby default
            rowstyle = tr[i].getAttribute("style");
            if (rowstyle == null) {
                checkvisible = true;
            }
            // If it is hidden, getAttribute returns "display: none;"
            else if (rowstyle.includes("none")) {
                checkvisible = false;
            }
            else {
                checkvisible = true;
            }

            // Maintains or hides the row
            if (filter == "nofilter") {
                tr[i].style.display = "";
            }
            else if ((txtValue == filter) amp; checkvisible == true) {
                tr[i].style.display = "";
            }
            else {
                tr[i].style.display = "none";
            }
        }
    }
}
 

Проблема

Способ myfilter работы допускает комбинированный фильтр, но с оговорками:

  • Для одного фильтра необходимо выполнить сброс до --ALL VALUES-- , прежде чем выбирать другой фильтр;
  • При объединении фильтров приведенное выше правило не позволяет мне динамически переключать значения, пока установлен другой фильтр.

Как я должен изменить функцию JavaScript, чтобы комбинированные фильтры работали должным образом (скажем, как фильтры Excel, где вы сохраняете один фильтр и меняете другой)? Я думал о разделении функции на три части, но в итоге получается та же ситуация.

Ответ №1:

Вы немного усложнили ситуацию, поместив данные в ячейки, а не в строки.

Несмотря на это, удаление onchange атрибутов из <select> элементов и использование этого, похоже, работает:

 const table = document.getElementById('mytable');
const filters = [
  ['formats', 'format'],
  ['models', 'model'],
  ['types', 'type'],
];
filters.forEach(v => v[0] = document.getElementById(v[0]));
const rows = Array.from(table.querySelectorAll('tr:not(:first-child)')).map(row => {
  return [row, filters.map(v => row.querySelector(`td[data-${v[1]}]`).dataset[v[1]])];
});
const changeHandler = event => {
  const targets = filters.map(v => v[0].value);
  rows.forEach(row => {
    row[0].style.display = targets.every(
      (v,i) => v == 'nofilter' || v == row[1][i]
    ) ? '' : 'none';
  });
};
filters.forEach(v => {
  v[0].addEventListener('change', changeHandler);
});
changeHandler();
 

filters представляет собой массив сопоставлений между select идентификатором и именем данных ячейки. Идентификатор преобразуется в <select> ссылки на элементы.

rows это сопоставление между строкой и ее данными. Это делает его более удобным для выполнения фильтрации.

changeHandler обрабатывает <select> change события. Он получает текущие <select> значения, а затем использует их для определения того, какие строки отображаются.

Затем мы подключаемся changeHandler к каждому <select> элементу и запускаем его один раз, чтобы синхронизировать все.

Комментарии:

1. Спасибо, я знаю, что моя реализация не будет оптимальной, я впервые работаю с JS. Просто из любопытства, как бы вы разместили этот фильтр, чтобы сделать его лучше? В итоге я выбрал data- атрибуты, потому что мой фильтр должен работать с ячейками без текстового содержимого. Если бы я реализовал это в <tr> элементах, вы бы по-прежнему рекомендовали использовать data- атрибут?

2. Атрибуты данных в порядке. Это зависит от запланированного срока службы таблицы и ее варианта использования. Если данные статичны или будут отображаться только редкие, незначительные изменения, тогда я бы поместил данные в строки (поскольку это то, что фильтруется), и я бы переместил стиль в таблицу стилей, основываясь на данных: например, #mytable tr[data-format="apple"] td:nth-child(1) {background-color: red;} . Таким образом, фильтр проще реализовать, а цвета автоматически выводятся из данных, поэтому их легко обновлять. Как новичок, я бы не стал сильно беспокоиться об этом, это то, что придет с практикой и изучением.