skip to Main Content

I’m building a table that will have several filters. Thus far, I’ve been able to build two filters that work in tandem. The filter values come from dropdown like this (I haven’t included "Org Type" as a filter yet:

enter image description here

enter image description here

Here is my code:

    function filterTable_all() {
            filter = []
            // get filter 1 values and push to array
            var input = document.getElementById("KCPWinner");
            var values = getSelectValues(input);
            filter.push(values.toString().toUpperCase().split(","));
            // get filter 2 values and push to array
            var input = document.getElementById("yearFounded");
            var values = getSelectValues(input);
            filter.push(values.toString().toUpperCase().split(","));
            // get table
            var table = document.getElementById("mytableID");
            var tr = table.getElementsByTagName("tr");
            // loop through rows for filtering
            for (var i = 1; i < tr.length; i++) {
                for (var j = 0; j < filter.length; j++) {
                   // if both filters are selected
                    if (filter[0] != "" && filter[1] != "") {
                        if (filter[0].includes(tr[i].children[5].textContent.toUpperCase().trim()) && filter[1].includes(tr[i].children[4].textContent.toUpperCase().trim())){
                        tr[i].style.display = "block";
                    }
                    
                    } // if one filter is selected
                    else if (filter[0] != "" && filter[1] == "") {
                        if (filter[0].includes(tr[i].children[5].textContent.toUpperCase().trim())){
                        tr[i].style.display = "block";
                    }

                    } // if the other filter is selected
                    else if (filter[0] == "" && filter[1] != "") {
                        if (filter[1].includes(tr[i].children[4].textContent.toUpperCase().trim())) {
                            tr[i].style.display = "block";
                        }
                    }
                }
            }
        }

Basically, I’m grabbing the values selected by the user, and determining if that value should be shown or not. This logic works for two filters.

However, it seems that the way I have done it will not scale well. I will want to add several more filters, but each filter will exponentially increase the if statements. Any ideas on how I can scale this filter logic?

These are the select buttons that are firing the filterTable_all() function (using Django):

<select class="submarket" style="" onchange='filterTable_all()' multiple name="yearFounded" id="yearFounded">
                  {% for year in filter_options.year_founded %}
                      {% if year|toStr in liveFilters.year_founded %}
                    <option class="submarketText checkedClass" value="{{ year }}" selected>{{ year }}</option>
                      {% else %}
                    <option class="submarketText" value="{{ year }}">{{ year }}</option>
                      {% endif %}
              {% endfor %}
              </select>

Edit: This is all front-end JS filtering

2

Answers


  1. Chosen as BEST ANSWER

    Ok, so @Tarek's response was super helpful, but the foo() function didn't quite work correctly for my use-case. Below is the code I used. It lets multiple filters filter in tandem:

    function foo(filter) {
        filterKeys = {
            'yearFounded': 4,
            'KCPWinner': 5
        };
        const table = document.getElementById("mytableID"); // get table
        const tr = table.getElementsByTagName("tr"); // get table elements
        const index = filterKeys[filter.id]; // Reference the filterKeys object to obtain the index that corresponds
        const values = getSelectValues(document.getElementById(filter.id));
        let otherValues = [];
        let otherIndex;
        let isOk = false;
        let isEmpty = false;
        if (values == "") {
            isEmpty = true;
        }
        // loop through each table element
        for (let i = 1; i < tr.length; i++) {
            // if the element at the corresponding index is included in the selected values or values is empty
            if (values.includes(tr[i].children[index].innerHTML || isEmpty)) {
                // loop through the rest of the filter keys object
                for (const key in filterKeys) {
                    otherValues = getSelectValues(document.getElementById(key));
                    // get the other index using the key and object
                    otherIndex = filterKeys[key]
                    if (otherValues != "") {
                        // if the other values do not include the child's corresponding index value and it's not empty then turn it off and break the loop: && values.includes(tr[i].children[index].innerHTML) && tr[i].children[otherIndex] != tr[i].children[index] && values != ""
                        if (otherValues.includes(tr[i].children[otherIndex].innerHTML)) {
                            console.log(" tr[i].style.display", tr[i].style.display)
                            console.log("hererer", tr[i].children[otherIndex].innerHTML)
                            isOk = true;
                        } else {
                            isOk = false;
                            break
                        }
                    }
                }
            }
    
    
            // if isOk is True then it meets all the requirements
            if (isOk) {
                tr[i].style.display = "flex" // turn it on
            } else {
                tr[i].style.display = "none" // turn it on
            }
            isOk = false; // set back to True for next iteration
    
    
    
        }
    } 
    

  2. Okay so new answer,
    After reviewing I realized your problem wasn’t so simple but I now have an answer

    I think your code is not only not scalable due to the issue that you would have exponential if statements, but also optimization. If you had millions of elements you would have to check every element against every filter value for absolutely every call which doesn’t make a lot of sense and would slow things down severely.

    Here’s my answer:

    Create an object that stores the corresponding index to filter.

    This will be easily changeable and can be appended if you add more filters.

    filterKeys = {
        'yearFounded': 4,
        'KCPWinner': 5
    };
    

    Now we can reference this in the function call which will make things more efficient.

    Secondly, we need to minimize the amount of loop instances that are occurring which will allow for the inclusion of large amounts of data.

    For example: If the year is changed, we only need to check against other filters IF the current element contains a year that is selected. So the preliminary check will be on the filter category that was changed. To do this we can pass in ‘this’ in the onchange event callback which references the current element that was changed.

    <select onchange = "foo(this)">SELECT</select>
    

    In javascript we need to loop through every element and check ONLY the index of the filter category that was changed — in this case — years. If it is contained in the current list of selected years, then and only then will it check against other filters. Also, we can minimize code by checking to see if the other filters contain a value that matches the value of the element and corresponding index. If that explanation doesn’t make a lot of sense hopefully it’ll be clearer with my example code:

    function foo (filter) {
        const table = document.getElementById("mytableID"); // get table
        const tr = table.getElementsByTagName("tr"); // get table elements
        const index = filterKeys[filter.id]; // Reference the filterKeys object to obtain the index that corresponds
        const values = getSelectValues(document.getElementById(filter.id));
        let otherValues = [];
        let otherIndex;
        let isOk = True;
        let isEmpty = False;
    
        if (values = "") {
            isEmpty = True;
        }
    
        // loop through each table element
        for (let i = 0; i < tr.length; i++) {
            
            // if the element at the corresponding index is included in the selected values or values is empty
            if (values.includes(tr[i].children[index] || isEmpty) {
               
               // loop through the rest of the filter keys object
               for (const key in filterKeys) {
                    // get the other values using the key
                    otherValues = getSelectValues(document.getElementById(key));
                    // get the other index using the key and object
                    otherIndex = filterKeys[key]
                   // if the other values do not include the child's corresponding index value and it's not empty then turn it off and break the loop
                   if (!otherValues.includes(tr[i].children[otherIndex]) && otherValues != "") {
                       tr[i].style.display = "none" // turn it off
                       isOk = False;
                       break;
                   }
               }
               
               // if isOk is True then it meets all the requirements
               if (isOk) {
                   tr[i].style.display = "block"// turn it on
               }
               isOk = True; // set back to True for next iteration
             }
                  
            else {
               tr[i].style.display = "none"// turn it off because it does not meet the requirement
            }
        }
         
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search