skip to Main Content

So, I have 3 filter boxes. The first is named room, second is named color and the third is named staff.

For every filter box there are multiple values which I can choose, for example for color I can choose the value "blue" or for room "presidential". I pass the name of the filterbox and the chosen value in a function changeBox (for example name: color, value: blue).

With that information I want to filter an array. Of course I want to have multiple filters active, which only show the wanted results which are compatible to my activated filters.

So what I did was using a switch case for every possibility which looked like this:

changeBox(name, value) {
    if (name === "color") {
        switch (value) {
            case "blue":
                myArray = myArray.filter(item => item.color === "blue");
                break;
        }
    }
    if (name === "room") {
        switch (value) {
            case "presidential":
                myArray = myArray.filter(item => item.room === "presidential");
                break;
        }
    }
}

This works If I have only one filter active. The problem is now, if I activate a second filter it works too. But if I switch the second filter to another value, it doesn’t work. I know it’s because of the myArray.filter. It deletes every value in the array which doesn’t fit to my filter. How can I keep the data without actually overwrite it every time?

4

Answers


  1. You should have all of your filter values at once and filter with them:

    const myArray = [{color: 'blue', room: 'presidential'}, {color: 'red', room: 'unknown'}];
    
    const $filter = document.querySelector('.filter');
    $filter.addEventListener('change', changeBox);
    
    function changeBox() {
      const filter = [...$filter.querySelectorAll('select')].reduce((filter, $elem) => {
        if($elem.value){
          filter[$elem.name] = $elem.value;
        }
        
        return filter;
            
      }, {});
      
      console.log('filter:', JSON.stringify(filter));
      
      const filtered = myArray.filter(item => Object.entries(filter).every(([name, value]) => item[name] === value));
      console.log('filtered:', JSON.stringify(filtered));
      
      // use your filtered array and keep myArray to filter next time
    }
    <div class="filter">
    <select name="color">
    <option value="">-</option>
    <option value="blue">Blue</option>
    <option value="red">Red</option>
    </select>
    <select name="room">
    <option value="">-</option>
    <option value="presidential">Presidentail</option>
    <option value="unknown">Unknown</option>
    </select>
    </div>
    Login or Signup to reply.
  2. To answer your question:

    It deletes every value in the array which doesn’t fit to my filter.
    How can I keep the data without actually overwrite it every time?

    You can copy your data into a new variable:

    let myArray = [{color: 'blue', room: 'presidential'}, {color: 'yellow', room: 'regular'}];
    let filtered = [...myArray] // Use spreadoperator to deep clone object
    console.log(filtered)

    Now we can always work with filtered. At every start of the filter process we repeat this copy so we always start with the complete dataset.

    I would also advise to look into the improvements that the comments and answer have given you.

    Login or Signup to reply.
  3. Here is the complete flow of filtering.

    1. Collect the state of the filter by grabbing the values of the <select> elements
    2. Filter the data based on those filters
    3. Render the filtered data

    Note: I added "Green" option to show "No results". You can modify the function to pass this as a custom message.

    const
      filterEl = document.querySelector('.filter'),
      resultsEl = document.querySelector('.results');
    
    const allData = [
      { color: 'red'  ,  room: 'presidential' },
      { color: 'red'  ,  room: 'unknown'      },
      { color: 'blue' ,  room: 'presidential' },
      { color: 'blue' ,  room: 'unknown'      }
    ];
    
    const rendererFn = (item, targetEl) => {
      targetEl.append(Object.assign(document.createElement('li'), {
        textContent: JSON.stringify(item)
      }));
    };
    
    const filterData = (data, filter) => {
      const entries = Object.entries(filter);
      return data.filter(item =>
          entries.length === 0 ||
          entries.every(([name, value]) => item[name] === value));
    };
    
    const collectFilters = (selectElArr) =>
      selectElArr.reduce((filter, { name, value }) => {
        if (value) {
          filter[name] = value;
        }
        return filter;
      }, {});
    
    const renderData = (data, callbackFn, targetEl) => {
      targetEl.innerHTML = '';
      if (data.length === 0) {
        targetEl.append(Object.assign(document.createElement('p'), {
          textContent: 'No results'
        }));
      }
      for (let item of data) {
        callbackFn(item, targetEl);
      }
    };
    
    const onFilterChange = () => {
      const filter = collectFilters([...filterEl.querySelectorAll('select')]);
      const filteredData = filterData(allData, filter);
      renderData(filteredData, rendererFn, resultsEl);
    }
    
    filterEl.addEventListener('change', onFilterChange);
    onFilterChange(); // Cause render of all items
    .results li { font-family: monospace; }
    <div class="filter">
      <select name="color">
        <option value="">-</option>
        <option value="red">Red</option>
        <option value="blue">Blue</option>
        <option value="green">Green</option>
      </select>
      <select name="room">
        <option value="">-</option>
        <option value="presidential">Presidential</option>
        <option value="unknown">Unknown</option>
      </select>
    </div>
    <hr />
    <h2>Results</h2>
    <ul class="results"></ul>
    Login or Signup to reply.
  4. If you consider to decouple actual filtering logic from UI, you can design some sort of generic multiple filter that can be arbitrarily customized and used on any data.

    For example, you can implement it as a function that accepts a collection of filters, and a collection of options with which those filters should be used against the filtered array.

    function filterMultiple (array, filters, settings) {
        filters = Object
    
            // convert settings into [filterName, options] pairs
            .entries(settings)
    
            // select those for which filters[filterName] exists
            .filter(([filterName, ]) => typeof filters[filterName] == 'function')
    
            // convert into shortcut filter functions
            // with options implicitly applied to them
            .map(([filterName, options]) => {
                let fn = filters[filterName];
                return item => fn(item, options);
            });
    
        // filter input array by testing each item against every shortcut filter
        return array.filter(item => filters.every(fn => fn(item)));
    }
    
    const array = [
        { id: 0,  room: 'presidential', color: 'white'  },
        { id: 1,  room: 'presidential', color: 'black'  },
        { id: 2,  room: 'presidential', color: 'black'  },
        { id: 3,  room: 'presidential', color: 'black'  },
        { id: 4,  room: 'honeymoon',    color: 'red'    },
        { id: 5,  room: 'honeymoon',    color: 'yellow' },
        { id: 6,  room: 'honeymoon',    color: 'green'  },
        { id: 7,  room: 'honeymoon',    color: 'blue'   },
        { id: 8,  room: 'penthouse',    color: 'red'    },
        { id: 9,  room: 'penthouse',    color: 'yellow' },
        { id: 10, room: 'penthouse',    color: 'green'  },
        { id: 11, room: 'penthouse',    color: 'blue'   },
        { id: 12, room: 'penthouse',    color: 'white'  },
        { id: 13, room: 'penthouse',    color: 'black'  }
    ];
    
    const filters = {
        roomEqual:  (item, options) => item.room === options,
        roomSome:   (item, options) => options.some(value => item.room === value),
        colorEqual: (item, options) => item.color === options,
        colorSome:  (item, options) => options.some(value => item.color === value)
    };
    
    console.log(filterMultiple(array, filters, {
        colorEqual: 'white'
    }));
    
    console.log(filterMultiple(array, filters, {
        colorSome: ['red', 'yellow']
    }));
    
    console.log(filterMultiple(array, filters, {
        roomEqual: 'presidential',
        colorSome: ['purple', 'magenta', 'black']
    }));
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search