1

For reference, the full code can be obtained here

I have three select elements which should act as filters on my page, calling the myfilter function. Then, within a table, I use the data- attribute in some <td> elements to "categorise" them without having to set different classes:

<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">&nbsp;</td>
    <td data-model="film" style="background-color:#3ca971;width:10px">&nbsp;</td>
    <td data-type="water" style="background-color:#458985;width:10px">&nbsp;</td>
    <th>1</th>
    <td>Michael</td>
    <td>New York</td>
    <td>M</td>
  </tr>
</table>

The myfilter function, adapted from an example from W3Schools, retrieves the value of the selected listbox, determines which column it should look up and compares the custom data- attribute to the value in order to keep the rows visible or hide them:

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) & checkvisible == true) {
                tr[i].style.display = "";
            }
            else {
                tr[i].style.display = "none";
            }
        }
    }
}

The Problem

The way myfilter operates allows for combined filter, but with caveats:

  • On a single filter, one has to reset to --ALL VALUES-- before selecting another filter;
  • When combining filters, the above rule stops me from dynamically switching values while another filter is in place.

How must I change the JavaScript function in order to have the combined filters work as intended (say, like Excel filters, where you keep one filter and changes the other)? I thought about splitting the function in three, but it ends up with the same situation.

0

1 Answer 1

2

You made things quite a bit more complicated by putting the data on the cells rather than on the rows.

Regardless, removing the onchange attributes from the <select> elements and using this seems to work:

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 is an array of mappings between the select id and the cell data name. The id is converted into <select> element references.

rows is a mapping between the row and its data. This makes it more convenient for performing the filter.

changeHandler handles the <select> change events. It grabs the current <select> values and then uses them to determine which rows are shown.

Then we attached changeHandler to each <select> element and trigger it once to synchronize everything.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, I know my implementation wouldn't be optimal, it's my first time handling JS ever. Just out of curiosity, how would you lay out this filter to make it better? I ended up choosing the data- attributes because my filter has to work on cells with no text content. Were I to implement it in the <tr> elements, would you still recommend using the data- attribute?
Data attributes are fine. It depends on the planned life of the table & its use case. If the data is static or will only see rare, minor changes, then I'd put the data on the rows (since that's what's being filtered) & I'd move the styling into a stylesheet, basing it on the data: For example, #mytable tr[data-format="apple"] td:nth-child(1) {background-color: red;}. This way the filter is easier to implement & the colors are automatically derived from the data & so are easy to update. As a beginner, I wouldn't worry to much about it, these are things that will come with practice & study.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.