skip to Main Content

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.

2

Answers


  1. Chosen as BEST ANSWER

    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
    

  2. @klenwell’s answer is out of date. Here’s how I got it to work:

    I’m using:

    • Bootstrap v3.3.6
    • bloodhound 0.11.1
    • bootstrap3-typeahead 3.1.0
    • jQuery 2.2.0

    My model is called Destination.

    app/models/destination_search.rb:

    class DestinationSearch < Searchlight::Search
    
      def base_query
        Destination.all
      end
    
      def search_typeahead
        query.where("name ILIKE", "%#{typeahead}%")
      end
    
    end
    

    controller:

    class DestinationsController < APIController
    
      def typeahead
        render json: DestinationSearch.new(typeahead: params[:query]).results
      end
    
    end
    

    JS:

    var bloodhound = new Bloodhound({
      datumTokenizer: function (d) {
        return Bloodhound.tokenizers.whitespace(d.value);
      },
      queryTokenizer: Bloodhound.tokenizers.whitespace,
    
      remote: {
        url: '/api/destinations/typeahead?query=%QUERY',
        wildcard: "%QUERY",
      },
      limit: 10
    });
    bloodhound.initialize();
    
    $(document).ready(function () {
      $(".destination-typeahead").typeahead({
        source: function (query, process) {
          return bloodhound.search(query, process, process);
        },
      });
    });
    

    and in the view:

    <%= text_field_tag :destination, class: "destination-typeahead" %>

    It feels a little bit hacky how I’m passing the process method into bloodhound.search twice – this is because bloodhound#search takes two callbacks as arguments, one that deals with cached/prefetched data and one that deals with data pulled dynamically via AJAX. I might not be using #search 100% correctly, but this approach works, and it’s a simple start.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search