Laisser un commentaire

Backbone.js : recherche fulltext dans les collections

La plupart du temps, lorsque l’on effectue une recherche sur un site web, on envoie une requête au serveur – laquelle est directement adressée à la base de données – puis le serveur nous renvoie la réponse de cette recherche.

Cependant, avec les applications web, on possède souvent notre jeux de données sur le client (le navigateur), et une bonne partie de la navigation et des actions que l’on peut effectuer sont gérées directement au sein du navigateur en JavaScript. Backbone nous donne justement une structure et des outils pour construire de telles applications. Ainsi, puisque l’on dispose déjà des données dans le navigateur, il est logique d’effectuer notre recherche dans celui-ci plutôt que de le sous-traiter au serveur, avec comme inconvénient les temps de latence que l’on connait. Voyons comment effectuer une recherche directement dans nos collections Backbone.js

La méthode where de backbone

Backbone possède une méthode de recherche : where. Celle-ci est quand même quelque peu limitée. Elle permet de faire une recherche sur un seul attribut d’une collection, et ne permet pas d’ignorer la casse. Donc si vous faites une recherche par prénoms, que la première lettre du prénom est en majuscule dans votre collection et que ce n’est pas le cas dans votre champs de recherche, vous n’aurez aucun résultat…

var friends = new Backbone.Collection([
    {name: "Athos",      job: "Musketeer"},
    {name: "Porthos",    job: "Musketeer"},
    {name: "Aramis",     job: "Musketeer"},
    {name: "d'Artagnan", job: "Guard"},
]);

var musketeers = friends.where({job: "Musketeer"});

alert(musketeers.length); // renvoie 3

Refaisons les choses manuellement

La théorie n’est pas bien compliquée. On récupère notre chaine de recherche (la plupart du temps depuis un champ input), on la met en minuscule puis on la compare à nos divers attributs que l’on place aussi en minuscule.

Il faut donc créer une boucle afin d’effectuer notre recherche sur tous les modèles de notre collection.

Il y a deux manières d’effectuer la boucle. La méthode for in du javascript, ou la méthode .filter de Underscore qui concentre la boucle et la comparaison. Cette dernière méthode repose sur la boucle .each de Underscore. Elle est plus concise et donc présente une syntaxe un peu plus facile. Cependant, comme vous pouvez le constater dans le test que j’ai mis en place, elle est beaucoup moins performante (jusqu’à 90% !) que le for in en JavaScript pur. Je présenterai les deux syntaxe mais j’opte pour le for in. Quoi qu’il en soit, si votre collection n’est pas trop grande, la différence de performance ne se fait pas ressentir.

// méthode searchContact d'une vue
searchContact: function(e) {
    // on récupère le texte de recherche
    // on le met en minuscule et on enlève les espaces avant et après
    var search = $('#searchForm').val().toLowerCase().trim();

    // si le texte fait au moins un caractère
    if (search.length > 0) {
        var match = [];

        for (var id in this.collection.models) {
            // pour chacun des modèles, si la chaine de recherche est dans l'attribut "first"
            if (this.collection.models[id].attributes.first.toLowerCase().indexOf(search) !== -1) {
                // on ajoute le modèle dans notre tableau match
                match.push(this.collection.models[id]);
            }
        }

        // match contient maintenant un tableau de tous les modèles
        // dont l'attribut "first" contient la chaine de recherche
    }
}

La même chose mais avec _.filter :

searchContact: function(e) {
  var search = $('#searchForm').val().toLowerCase().trim();

  if (search.length > 0) {
    var match = _.filter(this.collection.models, function(item) {
      return item.attributes.first.toLowerCase().indexOf(search) !== -1;
    });
  }
}

Dernière chose, si vous voulez effectuer la recherche sur plusieurs attributs, rien de plus simple, il suffit d’ajouter dans le if, vos autres comparaisons :

[…]
for (var id in this.collection.models) {
    // pour chacun des modèles, si la chaine de recherche est dans l'attribut "first"
    if (this.collection.models[id].attributes.first.toLowerCase().indexOf(search) !== -1||
    this.collection.models[id].attributes.last.toLowerCase().indexOf(search) !== -1) {
        // on ajoute le modèle dans notre tableau match
        match.push(this.collection.models[id]);
    }
}
[…]

// si vous voulez comparer un tableau de valeurs, stringifiez le avant :
// this.collection.models[id].attributes.myArray.join().toLowerCase().indexOf(search) !== -1

N’hésitez pas à donner des exemples d’utilisations dans vos commentaires !

Commentaires

Rejoignez la discussion !

Vous pouvez utiliser Markdown pour les liens [ancre de lien](url), la mise en *italique* et en **gras**. Enfin pour le code, vous pouvez utiliser la syntaxe `inline` et la syntaxe bloc

```
ceci est un bloc
de code
```