Designer
Designer

Reputation: 895

Search filter function with non Latin characters using jquery

Based on a previous question of mine i am trying to build - with the help of the community - a search filter function that works even when the user types non Latin characters in the search input field (in this case Swedish letters).

This is what i have until now:

var langMap = {
'a' : 'å',
'a' : 'ä',
'o' : 'ö'
}

$('#search-items-box').keyup(function(){
   var valThis = $(this).val().toLowerCase();
   var filteredWord = valThis.split('').map(function(letter){
   	if (langMap[letter]) {
    	return langMap[letter];
    }
    return letter;
   }).join('');
   
    if(filteredWord == ""){
        $('.itemsList .m3-item').show();           
    } else {
        $('.itemsList .m3-item').each(function(){
            var text = $(this).text().toLowerCase();
            (text.indexOf(filteredWord) == 0) ? $(this).show() : $(this).hide();
        });
   }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<input placeholder="Search Me" id="search-items-box" type="text" /> 

<div class="itemsList">
<div class="m3-item">Orånge</div>
<div class="m3-item">Banäna</div>
<div class="m3-item">Potatö</div>
</div>

The problem is that the search doesn't work properly. If you try to type the three words you will notice that the results doesn't display.

JSFIDDLE Here

Upvotes: 0

Views: 364

Answers (2)

chriopp
chriopp

Reputation: 957

Here I propose a more data-centric approach. It is a question of normalizing your possible matches to your needs. According to your specification a normalized word should have the following characteristics:

  1. Replace all non ASCII characters with their ASCII approximation
  2. Everything should be lowercase

This is done in the function normalizeWord.

The rest of the code creates a dictionary that binds the original word to it's normalized representation. The filter function returns a list of the original words from your dataset, that are matched to the input.

Actually there is a little mismatch in binding the code to the DOM because this approach actually works on a given dataset. In real life I would just rebuild the HTML representation of the matches from the filtered list instead of showing and hiding already existing DOM elements.

$(function() {
  var langMap = {
    'å' : 'a',
    'ä' : 'a',
    'ö' : 'o'
  };

  var words = [
    'Orånge',
    'Banäna',
    'Potatö'
  ];

  // Create normalized words

  // The map with the original and normalized word
  var dictionary = {};

  // Normalization
  words.forEach(function(w) {
    var word = normalizeWord(w);
    dictionary[word] = w;
  });

  function normalizeWord(word) {
    for (key in langMap) {
      word = word.toLowerCase()
        .replace(new RegExp(key, 'g'), langMap[key]);
    }
    return word;
  }

  function filter(word) {
    var possibleMatch = normalizeWord(word);
    var result = [];
    for (normalizedWord in dictionary) {
      if (normalizedWord.indexOf(possibleMatch) === 0) {
        result.push(dictionary[normalizedWord]);
      }
    }
    return result;
  }

  // Test
  var results = [
    'b -> ' + filter('b'),
    'bs -> ' +  filter('bs'),
    'baNana -> ' + filter('baNana'),
    'Potatö ->  ' + filter('Potatö')
  ]

  console.log(results);

  // Binding to the DOM
  $('#search-items-box').keyup(function() {
    var value = $(this).val();
    var matches = filter(value);

    if (matches.length === 0) {
      $('.itemsList .m3-item').show();
      return;
    }

    $('.itemsList .m3-item').each(function() {
      var $this = $(this),
        value = $this.html();

      if (matches.indexOf(value) > -1) {
        $this.show();
      } else {
        $this.hide();
      }
    });
  });
});

Upvotes: 0

Apogee
Apogee

Reputation: 109

The first condition is altering the character input at every key stroke if the condition is satisfied:

if (langMap[letter]) {
    return langMap[letter];

Now, when you type in 'b', Banana appears in the search because 'b' is not part of your langMap array and hence, it is not modified. It searches for any word starting with 'b' and the search result is 'Banäna'

However, once you type 'a', Banana is no longer there because it was transformed into 'å'. As a result, it is searching for 'bå' which returns zero results (as expected).

On another note, you may also encounter another problem later on with your langMap array:

var langMap = {
    'a' : 'å',
    'a' : 'ä',
    'o' : 'ö'
};

You have one key, 'a', which defines two different values. One of the values will never be accessible.

Based on our discussion, here is my proposed solution:

  • Invert the keys and values for the langMap
  • Transform the compared text into latin characters

     var langMap = {
      "å":"a",
      "ä":"a",
      "ö":"o"
     }
    
     $('#search-items-box').keyup(function(){
      var valThis = $(this).val().toLowerCase();
      var filteredWord = getLatinWord(valThis);
    
      if(filteredWord == ""){
          $('.itemsList .m3-item').show();           
      } else {
          $('.itemsList .m3-item').each(function(){
              var text = $(this).text().toLowerCase();
              text = getLatinWord(text);
              (text.indexOf(filteredWord) === 0) ? $(this).show() : $(this).hide();
          });
      }
     });
    
     function getLatinWord(word){
       return word.split('').map(function(character){
                              if (langMap[character]) {
                                return langMap[character];
                              }
                              return character;
                           }).join('');
     }
    

Upvotes: 1

Related Questions