Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Rails, how do I set up the Twitter Typeahead.js gem for Rails?

The bootstrap-typeahead-rails gem's README kicks the question over to Twitter's typeahead.js README. This left much to be desired.

This Stack Overflow answer provides detailed instructions for the twitter-typeahead-rails gem. I wanted to see something like that for the bootstrap-typeahead-rails gem.

like image 765
klenwell Avatar asked May 03 '15 21:05

klenwell


1 Answers

Here's my guide. It follows @ihaztehcodez's example. This example assumes a model Thing and adds a form to the index view for searching things by the model's name attribute.

A few notes:

  • I'm using Rails 4 (4.2.1).
  • For search queries, I'm using the Searchlight gem.
  • For templates, I'm using the slim-rails gem.
  • Styling is left as an exercise for the developer.

Add gem to gemfile

# Gemfile

# Typeahead gem
gem 'bootstrap-typeahead-rails'

# Optional gems
gem 'searchlight'
gem 'slim-rails'

Include typeahead files in asset manifests

Stylesheet (SASS)

# app/assets/stylesheets/application.scss

  *= require bootstrap-typeahead-rails

Javascript

# app/assets/javascripts/application.js

//= require bootstrap-typeahead-rails
//= require_tree .

Add typeahead route to routes file

# config/routes.rb

  get 'things/typeahead/:query' => 'things#typeahead'

Add typeahead javascript code

# app/assets/javascripts/things.js

var onReady = function() {

  // initialize bloodhound engine
  var searchSelector = 'input.typeahead';

  var bloodhound = new Bloodhound({
    datumTokenizer: function (d) {
      return Bloodhound.tokenizers.whitespace(d.value);
    },
    queryTokenizer: Bloodhound.tokenizers.whitespace,

    // sends ajax request to remote url where %QUERY is user input
    remote: '/things/typeahead/%QUERY',
    limit: 50
  });
  bloodhound.initialize();

  // initialize typeahead widget and hook it up to bloodhound engine
  // #typeahead is just a text input
  $(searchSelector).typeahead(null, {
    displayKey: 'name',
    source: bloodhound.ttAdapter()
  });

  // this is the event that is fired when a user clicks on a suggestion
  $(searchSelector).bind('typeahead:selected', function(event, datum, name) {
    //console.debug('Suggestion clicked:', event, datum, name);
    window.location.href = '/things/' + datum.id;
  });
};

Add relevant methods/actions to controller

# app/controllers/things_controller.rb

  # GET /things
  # GET /things.json
  def index
    @search = ThingSearch.new(search_params)
    @things = search_params.present? ? @search.results : Thing.all
  end

  # GET /things/typeahead/:query
  def typeahead
    @search  = ThingSearch.new(typeahead: params[:query])
    render json: @search.results
  end

  private

  def search_params
    params[:thing_search] || {}
  end

Add search form to index view (using SLIM gem)

# app/views/things/index.html.slim

div.search.things
  = form_for @search, url: things_path, method: :get do |f|
    div.form-group.row
      div.col-sm-3
      div.col-sm-6
        = f.text_field :name_like, {class: 'typeahead form-control',
            placeholder: "Search by name"}
        = f.submit 'Search', {class: 'btn btn-primary'}
      div.col-sm-3.count
        | Showing <strong>#{@things.length}</strong> Thing#{@things.length != 1 ? 's' : ''}

Create Searchlight search class

If you prefer not to use Searchlight, use the ActiveRecord query interface in the model.

# app/searches/thing_search.rb

class ThingSearch < Searchlight::Search
  search_on Thing.all

  searches :name_like, :typeahead

  # Note: these two methods are identical but they could reasonably differ.
  def search_name_like
    search.where("name ILIKE ?", "%#{name_like}%")
  end

  def search_typeahead
    search.where("name ILIKE ?", "%#{typeahead}%")
  end
end
like image 177
klenwell Avatar answered Nov 01 '22 16:11

klenwell