Isnor Creative Blog
Ruby on Rails, Ember, Phoenix, Elixir, React

How I set up javascript full text search for an Ember application

I’ve been building mobile applications for iOS, Android and PWA with Ember, that rely on no external API for data. They don’t even rely on Ember Data, just POJOs. This is how I set up full text search for one such app.

Elasticlunr and ember-browserify to the rescue!

I discovered an NPM package called elasticlunr that promised lightweight, full-text search.

Sounds good right? It delivered on its promise!

I’m thinking maybe I could create an Ember addon to more easily plug this into an Ember app, but it was frankly very easy anyway, just a yarn add away. Note that I also added ember-browserify to make it easy to import elasticlunr.

Initializer

Next I added an initializer for each search type, plants for example:

export function initialize(application) {
  application.inject('route', 'lunr', 'service:plant-search');
}
export default {
  name: 'plantSearch',
  initialize
};

Service

And then I created a corresponding service.

The service imports my plant data POJO, as well as elasticlunr. It then sets up a search index, adding fields and the plant ID field as reference returned in search results.

The search function takes a search term (weighted fields could be an additional param here, see elasticlunr docs for info) and then performs an elasticlunr search. When it gets results, it then matches those ID references up to plant IDs in my POJO.

import Ember from 'ember';
import Plants from 'wildcrafting/plant/data';
import elasticlunr from 'npm:elasticlunr';

export default Ember.Service.extend({

  index: null,

  setup: function() {
    let index = elasticlunr(function () {
      this.addField('name');
      this.addField('latinName');
      // etc fields you want to make searchable
      this.setRef('id');
    });

    Plants.forEach(item => index.addDoc(item));
    this.set('index', index);
  }.on('init'),

  search(term) {
    let results = this.get('index').search(term);
    let plants = [];
    results.forEach(result => {
      let plant = Plants.find(item => item.id === parseInt(result.ref));
      plants.push(plant);
    });
    return plants;
  }
});

Controller

In my controller I inject the search service, and then do the search. It boils down to two lines of code:

// inject the service
plantSearch: Ember.inject.service(),

// do the search
items = this.get('plantSearch').search(term);

Wrapping up…

This was very fast to implement, and worked. It’s not a huge data set –– but I was nevertheless impressed with how fast searches work and the search results are great!

It’s probably an unusual and relatively simple use case, but I can imagine this basic idea being modified to suit a variety of other situations.

Gordon B. Isnor writes about Ruby on Rails, Ember.js, Elixir, Phoenix, React, and web development in general. If you enjoyed this article, join his newsletter.

comments powered by Disqus