Tuesday, January 17, 2023
HomeWeb DevelopmentThe best way to Set Up a Multi-Filter Function with Vanilla JavaScript

The best way to Set Up a Multi-Filter Function with Vanilla JavaScript


On this tutorial, we’re going to make use of vanilla JavaScript to implement a multi-filter characteristic, that enables a consumer to pick and mix a number of completely different filter varieties.

Filtering is a generally used characteristic on most user-centric web sites corresponding to e-commerce websites or weblog pages. It permits a consumer slender down content material utilizing particular situations.

In a earlier tutorial, we checked out the way to filter information from a listing utilizing a single parameter. 

This methodology served as a great introduction on the way to filter information in a listing however most real-life circumstances require a extra complicated methodology of filtering.

Take an internet site that sells garments. Customers would require the flexibility to filter by shade, dimension, product sort and many others. It’s additionally attainable to wish to choose completely different filters in the identical class. For instance, within the above demo recreating an article web page, a consumer would possibly wish to see all articles tagged with “JavaScript” and “CSS” versus only one tag. 

1. Structure & Styling

We’ll be utilizing the identical structure and styling from the earlier tutorial and updating our HTML to incorporate a Classes and Degree container. We’ll additionally embody an article depend component after the title for our articles and a container to show a “no outcomes discovered” message. That is our up to date HTML.

1
<major class="container"https://webdesign.tutsplus.com/tutorials/>
2
  <div class="container__title"https://webdesign.tutsplus.com/tutorials/>Classes</div>
3
  <div id="post-categories" class="filter-container"https://webdesign.tutsplus.com/tutorials/></div>
4
  <div class="container__title" >Degree</div>
5
  <div id="post-level" class="filter-container"https://webdesign.tutsplus.com/tutorials/></div>
6
  <div class="container__title"https://webdesign.tutsplus.com/tutorials/>
7
    Tutorials (<span id="post-count"https://webdesign.tutsplus.com/tutorials/></span>)
8
  </div>
9
  <div id="posts-container"https://webdesign.tutsplus.com/tutorials/></div>
10
  <p id="no-posts"https://webdesign.tutsplus.com/tutorials/></p>
11
</major>

2. Displaying Knowledge With JavaScript

In a earlier tutorial, we mentioned scraping information from the Tuts+ writer web page to create a mock information object for our demo. We’ll be utilizing the Fetch API to retrieve the scraped information saved in a Github gist.

That is what our information object seems to be like:

1
[
2
  {
3
    "https://webdesign.tutsplus.com/tutorials/title"https://webdesign.tutsplus.com/tutorials/: ""https://webdesign.tutsplus.com/tutorials/,
4
    "https://webdesign.tutsplus.com/tutorials/link"https://webdesign.tutsplus.com/tutorials/: ""https://webdesign.tutsplus.com/tutorials/,
5
    "https://webdesign.tutsplus.com/tutorials/image"https://webdesign.tutsplus.com/tutorials/: ""https://webdesign.tutsplus.com/tutorials/,
6
    "https://webdesign.tutsplus.com/tutorials/categories"https://webdesign.tutsplus.com/tutorials/: [""https://webdesign.tutsplus.com/tutorials/],
7
    "https://webdesign.tutsplus.com/tutorials/stage"https://webdesign.tutsplus.com/tutorials/: ""
8
  },
9
  ...
10
 ]

That is the script for fetching information from the script:

1
fetch("https://webdesign.tutsplus.com/tutorials/https://gist.githubusercontent.com/jemimaabu/564beec0a30dbd7d63a90a153d2bc80b/uncooked/12741cce73c71c179381cc9e3b5f79988ea76a45/tutorial-levels"
2
).then(async (response) => {
3
  // deal with response information
4
});

As soon as we’ve gotten our fetched information, we are able to then manipulate the info and append it on the web page.

3. Appending Knowledge to the Webpage

For every object in our fetched response, we’ll create a publish div that can show the info on the web page. First, let’s outline our world variables.

We’ll create a postsData variable to retailer our fetched information.

1
let postsData = ""https://webdesign.tutsplus.com/tutorials/;

Since this tutorial makes use of a number of filters, we’ll additionally want a currentFilters variable to retailer which filters are at present chosen:

1
let currentFilters = {
2
  classes: [],
3
  stage: []
4
};

Now we’ll create variables for our present parts:

1
const postsContainer = doc.querySelector("https://webdesign.tutsplus.com/tutorials/#posts-container"https://webdesign.tutsplus.com/tutorials/);
2
const categoriesContainer = doc.querySelector("https://webdesign.tutsplus.com/tutorials/#post-categories"https://webdesign.tutsplus.com/tutorials/);
3
const levelsContainer = doc.querySelector("https://webdesign.tutsplus.com/tutorials/#post-levels"https://webdesign.tutsplus.com/tutorials/);
4
const postCount = doc.querySelector("https://webdesign.tutsplus.com/tutorials/#post-count"https://webdesign.tutsplus.com/tutorials/);
5
const noResults = doc.querySelector("https://webdesign.tutsplus.com/tutorials/#no-results"https://webdesign.tutsplus.com/tutorials/);

Then we’ll create a operate createPost() that can deal with appending a brand new div to the postsContainer component. On this operate, we create a brand new div component with the classname “publish” and set the innerHTML as the info we wish to show.

1
const createPost = (postData) => {
2
  const { title, hyperlink, picture, classes, stage } = postData;
3
  const publish = doc.createElement("https://webdesign.tutsplus.com/tutorials/div"https://webdesign.tutsplus.com/tutorials/);
4
  publish.className = "https://webdesign.tutsplus.com/tutorials/publish"https://webdesign.tutsplus.com/tutorials/;
5
  publish.innerHTML = `
6
      <a category="post-preview" href="https://webdesign.tutsplus.com/tutorials/${hyperlink}" goal="_blank">
7
        <img class="post-image" src="https://webdesign.tutsplus.com/tutorials/${picture}">
8
      </a>
9
      <div class="post-content">
10
        <p class="post-title">${title}</p>
11
        <div class="post-tags">
12
          ${classes
13
            .map((class) => {
14
              return "https://webdesign.tutsplus.com/tutorials/<span class="post-tag">' + class + "https://webdesign.tutsplus.com/tutorials/</span>"https://webdesign.tutsplus.com/tutorials/;
15
            })
16
            .be part of(""https://webdesign.tutsplus.com/tutorials/)}
17
        </div>
18
        <div class="post-footer">
19
          <span class="post-level">${stage}</span>
20
        </div>
21
      </div>
22
  `;
23
24
  postsContainer.append(publish);
25
};

Inside our publish innerHTML, we use the be part of("") methodology on our classes.map() to take away the ‘,’ image that’s included in each array.

Now we are able to replace our response operate to name the createPost() operate as soon as the info has been fetched and in addition replace our postCount component:

1
fetch("https://webdesign.tutsplus.com/tutorials/https://gist.githubusercontent.com/jemimaabu/b89339c1b7e5f81f8737fb66a858b6fc/uncooked/cdded4a10dbc98858481b5aedbcce3f3026dc271/tutorials"
2
).then(async (response) => {
3
  postsData = await response.json();
4
  postsData.map((publish) => createPost(publish));
5
  postCount.innerText = postsData.size;
6
});

4. Get Filter Params from Response

Since we’re utilizing JavaScript, we are able to map by way of our response to create a dynamic listing of filter params.

We’ll write a script that kinds by way of the classes and stage keys in every response object and returns a novel listing. We are able to replace our response object to deal with getting a novel listing of filter params.

One factor to notice is that the classes and stage keys are of various information varieties so that they’ll should be dealt with in another way. Classes is an array and stage is a string.

1
fetch(
2
  "https://webdesign.tutsplus.com/tutorials/https://gist.githubusercontent.com/jemimaabu/564beec0a30dbd7d63a90a153d2bc80b/uncooked/12741cce73c71c179381cc9e3b5f79988ea76a45/tutorial-levels"
3
).then(async (response) => {
4
  postsData = await response.json();
5
  postsData.map((publish) => createPost(publish));
6
  postCount.innerText = postsData.size;
7
8
  categoriesData = [
9
    ...new Set(
10
      postsData
11
        .map((post) => post.categories)
12
        .reduce((acc, curVal) => acc.concat(curVal), [])
13
    )
14
  ];
15
16
  levelData = [...new Set(postsData.map((post) => post.level))];
17
});

Breaking down the code for our categoriesData:

  • We use [... new Set] to create an array of distinctive values. Set returns an object of distinctive values and the unfold syntax […] converts the thing into an array.
  • We map by way of the postsData to get the classes array of every publish object inside the info response.
  • We use the .cut back() methodology to mix the classes array for every publish object into one array.

Since stage is a string, we don’t want to scale back it down to 1 array so we are able to simply create a brand new array utilizing the Set object and map methodology.

As soon as we’ve gotten our array of distinctive filter values from the publish outcomes, we are able to create a operate to append every filter to the web page.

We’ll create a brand new button component and set the innerText in response to the filter worth. We’ll additionally have to consider what sort of filter it’s (both a class filter or a stage filter).

Every filter button may have a click on occasion listener set to the handleButtonClick operate, which might be chargeable for dealing with the filtering logic. We’ll additionally set a “data-state” attribute to deal with altering the button state when clicked. 

1
const createFilter = (key, param, container) => {
2
  const filterButton = doc.createElement("https://webdesign.tutsplus.com/tutorials/button"https://webdesign.tutsplus.com/tutorials/);
3
  filterButton.className = "https://webdesign.tutsplus.com/tutorials/filter-button"https://webdesign.tutsplus.com/tutorials/;
4
  filterButton.innerText = param;
5
  filterButton.setAttribute("https://webdesign.tutsplus.com/tutorials/data-state"https://webdesign.tutsplus.com/tutorials/, "https://webdesign.tutsplus.com/tutorials/inactive"https://webdesign.tutsplus.com/tutorials/);
6
  filterButton.addEventListener("https://webdesign.tutsplus.com/tutorials/click on"https://webdesign.tutsplus.com/tutorials/, (e) =>
7
    handleButtonClick(e, key, param, container)
8
  );
9
10
  container.append(filterButton);
11
};

createFilter takes the next params and passes them to the handleButtonClick operate:

  • key: The filter key of the response object (class or stage)
  • param: The corresponding worth of the important thing (e.g. publish[0].stage=”Newbie”)
  • container: The container component the filter might be appended to.

The response operate can now be up to date to name the createFilter() operate:

1
fetch(
2
  "https://webdesign.tutsplus.com/tutorials/https://gist.githubusercontent.com/jemimaabu/564beec0a30dbd7d63a90a153d2bc80b/uncooked/12741cce73c71c179381cc9e3b5f79988ea76a45/tutorial-levels"
3
).then(async (response) => {
4
  postsData = await response.json();
5
  postsData.map((publish) => createPost(publish));
6
  postCount.innerText = postsData.size;
7
8
  categoriesData = [
9
    ...new Set(
10
      postsData
11
        .map((post) => post.categories)
12
        .reduce((acc, curVal) => acc.concat(curVal), [])
13
    )
14
  ];
15
  categoriesData.map((class) =>
16
    createFilter("https://webdesign.tutsplus.com/tutorials/classes"https://webdesign.tutsplus.com/tutorials/, class, categoriesContainer)
17
  );
18
19
  levelData = [...new Set(postsData.map((post) => post.level))];
20
  levelData.map((stage) => createFilter("https://webdesign.tutsplus.com/tutorials/stage"https://webdesign.tutsplus.com/tutorials/, stage, levelsContainer));
21
});

5. Deal with Button Click on 

Now we’ve gotten our filter buttons and preliminary information, we are able to outline a operate to deal with filtering the info when a button is clicked. That is the place the majority of our filtering element might be dealt with.

We wish to detect when a button has been clicked and replace the button state. On this tutorial, we’ll toggle the buttons so if clicked as soon as, the button is about to lively and if clicked once more, the button is about to inactive. Let’s break down how we’ll be dealing with the button click on:

  1. If an inactive button is clicked, we add the present param to the suitable key in our currentFilters object. For instance, if the filter button Animation beneath Classes is clicked, our currentFilters object ought to appear like { classes: ['Animation'], stage: [] }; We’ll additionally replace the model of the button to its lively state.
  2. If the button is already lively, we’ll set it to inactive and take away the parameter from the corresponding key in currentFilters. We are able to do that by filtering the currentFilters key.
  3. After we’ve up to date the button state, we’ll deal with filtering posts primarily based on the values within the currentFilters object.

We are able to now outline our handleButtonClick operate:

1
const handleButtonClick = (e, key, param, container) => {
2
  const button = e.goal;
3
  const buttonState = button.getAttribute("https://webdesign.tutsplus.com/tutorials/data-state"https://webdesign.tutsplus.com/tutorials/);
4
  if (buttonState == "https://webdesign.tutsplus.com/tutorials/inactive"https://webdesign.tutsplus.com/tutorials/) {
5
    button.classList.add("https://webdesign.tutsplus.com/tutorials/is-active"https://webdesign.tutsplus.com/tutorials/);
6
    button.setAttribute("https://webdesign.tutsplus.com/tutorials/data-state"https://webdesign.tutsplus.com/tutorials/, "https://webdesign.tutsplus.com/tutorials/lively"https://webdesign.tutsplus.com/tutorials/);
7
    currentFilters[key].push(param);
8
    handleFilterPosts(currentFilters);
9
  } else {
10
    button.classList.take away("https://webdesign.tutsplus.com/tutorials/is-active"https://webdesign.tutsplus.com/tutorials/);
11
    button.setAttribute("https://webdesign.tutsplus.com/tutorials/data-state"https://webdesign.tutsplus.com/tutorials/, "https://webdesign.tutsplus.com/tutorials/inactive"https://webdesign.tutsplus.com/tutorials/);
12
    currentFilters[key] = currentFilters[key].filter((merchandise) => merchandise !== param);
13
    handleFilterPosts(currentFilters);
14
  }
15
};

Let’s check out how the currentFilters get up to date primarily based on the button state:

6. Defining Multi-filters

One factor to bear in mind when manipulating information is to keep away from mutation. We wish to be certain that our unique array doesn’t get modified once we’re filtering.

First we’ll create a brand new occasion of our postsData variable known as filteredPosts to keep away from mutating the unique array.

1
let filteredPosts = [...postsData];

Now we are able to work on the filtering logic. Remember the fact that our postsData returns two completely different information varieties for the filters: classes is an array and stage is a string.

We wish to be sure that our filter element works for these two completely different information varieties.

For the classes array in currentFilter, we’ll have to first we test if the array isn’t empty utilizing filters.classes.size > 0

Now we’ll be filtering the publish classes by the currentFilters classes. We wish to return any publish the place any of the values in publish[categories] are additionally current in currentFilters[categories].

We are able to do that utilizing the .filter() and .some methodology.

Thesome()methodology assessments whether or not at the least one component within the array passes the check carried out by the offered operate. – MDN

We’ll filter the publish array to return any publish the place a number of the values in publish.classes are included in our currentFilters.classes. Right here’s what the code seems to be like:

1
 if (filters.classes.size > 0) {
2
    filteredPosts = filteredPosts.filter((publish) =>
3
      publish.classes.some((class) => {
4
        return filters.classes.contains(class);
5
      })
6
    );
7
  }

Then we’ll go the filteredPosts into our stage filter as nicely. That is the place the multi filter logic is available in as we solely perform the filter logic on the already filtered worth.

When filtering with a string, we simply have to test if the publish.stage worth is included in our currentFilters.stage array

1
  if (filters.stage.size > 0) {
2
    filteredPosts = filteredPosts.filter((publish) =>
3
      filters.stage.contains(publish.stage)
4
    );
5
  }

For a extra superior filter implementation, we are able to compress our classes filter and stage filter capabilities into one. 

1
  let filterKeys = Object.keys(filters);
2
3
  filterKeys.forEach((key) => {
4
    let currentKey = filters[key]
5
    if (currentKey.size <= 0) {
6
      return;
7
    }
8
9
    filteredPosts = filteredPosts.filter((publish) => {
10
      let currentValue = publish[key]
11
      return Array.isArray(currentValue)
12
        ? currentValue.some((val) => currentKey.contains(val))
13
        : currentKey.contains(currentValue);
14
    });
15
  });

Within the above operate, we:

  • Get the important thing values in our currentFilters object. This can return an array ["categories", "level"]
  • Map by way of our filterKeys array and carry out an early return if the currentKey is an empty array
  • Perform the filter operate on our filteredPosts array relying on if the present filter worth is an array or not.

Lastly we are able to outline our multi-filter logic:

  • Create a operate handleFilterPosts() that accepts a filters param
  • Set filteredPosts by all of the values in our currentFilters array.
  • We’ll additionally replace our postCount and noResults parts primarily based on the info in filteredPosts
  • Lastly, we’ll clear all parts in posts-container and append the brand new filteredData to the container.
1
const handleFilterPosts = (filters) => {
2
  let filteredPosts = [...postsData];
3
  let filterKeys = Object.keys(filters);
4
5
  filterKeys.forEach((key) => {
6
    let currentKey = filters[key]
7
    if (currentKey.size <= 0) {
8
      return;
9
    }
10
11
    filteredPosts = filteredPosts.filter((publish) => {
12
      let currentValue = publish[key]
13
      return Array.isArray(currentValue)
14
        ? currentValue.some((val) => currentKey.contains(val))
15
        : currentKey.contains(currentValue);
16
    });
17
  });
18
19
  postCount.innerText = filteredPosts.size;
20
21
  if (filteredPosts.size == 0) {
22
    noResults.innerText = "https://webdesign.tutsplus.com/tutorials/Sorry, we could not discover any outcomes."https://webdesign.tutsplus.com/tutorials/;
23
  } else {
24
    noResults.innerText = ""https://webdesign.tutsplus.com/tutorials/;
25
  }
26
27
  postsContainer.innerHTML = ""https://webdesign.tutsplus.com/tutorials/;
28
  filteredPosts.map((publish) => createPost(publish));
29
};

Conclusion

And with this, we’ve arrange our multi-filter implementation!

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments