Blog
Published on

How to search a list with accented characters in Vue

Authors

In most projects you end up having lists that need to be searchable. A list of countries for instance or a list of entries.

I work a lot with French clients that have lots of accented characters like àéèêë etc.. So when I search for "eco" I want that to match with "éco" as well.

The first step is to create a computed property for your filtered list.

export default {
 data () {
   return {
     entries: [
       { title: "Le magazine de l'éco" },
       { title: "La recette de la crème à l'ail" }
     ],
     search: ''
   }
 },
  
  computed: {
    filteredEntries () {
   	  return this.entries   
    }
  }
}

Now we need to filter the entries based on the search value.

filteredEntries () {
  if (!this.search) {
    return this.entries
  }
  
  return this
    .entries
    .filter(entry => entry.title.indexOf(this.search) >= 0)
}

This will just check if there is a search value and if it is included in the entry's title.

We want to make this search case insensitive.

filteredEntries () {
  if (!this.search) {
    return this.entries
  }
  
  const search = this.search.toLowerCase()
  
  return this
    .entries
    .filter(entry => entry.title.toLowerCase().indexOf(search) >= 0)
}

Now we want to support accented characters so we need to normalize them. For that we'll use the normalize function which returns an Unicode version (basically it transforms accented characters into two characters, the base character and the accent) of the string and then we replace the accents characters.

In short it replaces é by e ́ and then we remove the ́.

filteredEntries () {
  if (!this.search) {
    return this.entries
  }
  
  const normalizedSearch = this.search.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase()
  
  return this
    .entries
    .filter(entry => entry.title.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().indexOf(normalizedSearch) >= 0)
}

Now we want to support multiple search terms you need to adapt the code.

Either you want to return all entries matching at least one search term :

filteredEntries () {
  if (!this.search) {
    return this.entries
  }
  
  const normalizedSearchTerms = this.search.split(' ').map(term => {
    return term.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase()
  })
  
  return this
    .entries
    .filter(entry => {
    	return normalizedSearchTerms
          	.some(term => entry.title.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().indexOf(term) >= 0)
    })
}

Either you want to return all entries matching all search terms :

filteredEntries () {
  if (!this.search) {
    return this.entries
  }
  
  const normalizedSearchTerms = this.search.split(' ').map(term => {
    return term.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase()
  })
  
  return this
    .entries
    .filter(entry => {
    	return normalizedSearchTerms
          	.every(term => entry.title.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().indexOf(term) >= 0)
    })
}

That's it, you now have a multi-terms search supporting accented characters.

Here's a code sandbox with the code of this post.