Search code examples
javascriptjquerybackbone.jsfull-text-searchunderscore.js

Searching for text inside nested object (Backbone.js collection as example)


I have a backbone.js collection where I need to do a fulltextsearch on. The tools I have at hand are the following:

Backbone.js, underscore.js, jQuery

For those not familiar with backbone:

A backbones collection is just an object. Inside the collection there is an array with models. Each model has an array with attributes. I have to search each attribute for a string.

The code I am using for this is:

query = 'some user input';

query = $.trim(query);
query = query.replace(/ /gi, '|');

var pattern = new RegExp(query, "i");

// this.collection.forEach is the same as _.each
// only it get's the models from the collection
this.collection.forEach(function(model) {
    var check = true;
    _.each(model.attributes, function(attr){
        if(pattern.test(attr) && check){
            // Do something with the matched item
            check = false;
        }
    }, this);
}, this);

Maybe one of the tools I am using has a better way of dealing with this ?


Solution

  • Backbone extends a lot of the underscore methods into the Collection class so you can get rid of some of that stuff. Really you probably want to impliment this on the collection itself as a method, then I would probably look at those keys using a good old fashioned for loop, especially if I wanted to break out of it.

    // in Backbone.Collection.extend
    search: function( query, callback ){
      var pattern = new RegExp( $.trim( query ).replace( / /gi, '|' ), "i");
      var collection = this;
      collection.each(function(model) {
          for( k in model.attributes ){
            if( model.attributes.hasOwnProperty(k) && pattern.test(model.attributes[k]) ){ 
              callback.call( collection, model, k ); 
              break; // ends the for loop.
            }
          }
      });
    
    }
    
    // later
    collection.search('foo', function( model, attr ){
      console.log('found foo in '+model.cid+' attribute '+attr);
    });
    

    That said, this would only ever return the first match from the collection. You may prefer an implementation that returns an array of results as [model, attribute] pairs.

    // in Backbone.Collection.extend
    search: function( query, callback ){
      var matches = [];
      var pattern = new RegExp( $.trim( query ).replace( / /gi, '|' ), "i");
      this.each(function(model) {
          for( k in model.attributes ){
            if( model.attributes.hasOwnProperty(k) && pattern.test(model.attributes[k]) ){ 
              matches.push([model, k]);
            }
          }
      });
      callback.call( this, matches );
    }
    
    // later
    collection.search('foo', function( matches ){
      _.each(matches, function(match){
        console.log('found foo in '+match[0].cid+' attribute '+match[1]);
      });
    });
    

    Or, if you wanted an array of models which matched but don't care which attribute matched you can use filter

    // in Backbone.Collection.extend
    search: function( query, callback ){
      var pattern = new RegExp( $.trim( query ).replace( / /gi, '|' ), "i");
      callback.call( this, this.filter(function( model ){ 
        for( k in model.attributes ){ 
          if( model.attributes.hasOwnProperty(k) && pattern.test(k) ) 
            return true;
        }
      }));
    }
    
    // later
    collection.search('foo', function( matches ){
      _.each(matches, function(match){
        console.log('found foo in '+match[0].cid+' somewhere');
      });
    });