diff --git a/Gemfile b/Gemfile
index e4acd80a..d792d5f0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -71,4 +71,4 @@ gem 'squash_ruby', require: 'squash/ruby'
gem 'squash_rails', require: 'squash/rails'
gem 'sitemap_generator', '~> 5.0.5'
gem 'newrelic_rpm'
-
+gem 'twitter-typeahead-rails'
diff --git a/Gemfile.lock b/Gemfile.lock
index e2ea00e2..db6a51bc 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -294,6 +294,10 @@ GEM
thread_safe (0.3.5)
tilt (1.4.1)
tins (1.3.4)
+ twitter-typeahead-rails (0.10.5)
+ actionpack (>= 3.1)
+ jquery-rails
+ railties (>= 3.1)
tzinfo (1.2.2)
thread_safe (~> 0.1)
uglifier (2.7.0)
@@ -343,4 +347,5 @@ DEPENDENCIES
squash_rails
squash_ruby
therubyracer
+ twitter-typeahead-rails
uglifier (>= 1.3.0)
diff --git a/Rakefile b/Rakefile
index 18f6ee3b..5c82e066 100644
--- a/Rakefile
+++ b/Rakefile
@@ -5,7 +5,7 @@ require File.expand_path('../config/application', __FILE__)
Rails.application.load_tasks
-BLACKLIGHT_JETTY_VERSION = '4.10.2'
+BLACKLIGHT_JETTY_VERSION = '4.10.4'
ZIP_URL = "https://github.com/projectblacklight/blacklight-jetty/archive/v#{BLACKLIGHT_JETTY_VERSION}.zip"
require 'jettywrapper'
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index d815aa61..797b5aea 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -17,3 +17,5 @@
// Required by Blacklight
//= require blacklight/blacklight
//= require_tree .
+
+//= require twitter/typeahead.min
diff --git a/app/assets/javascripts/modules/autocomplete.js b/app/assets/javascripts/modules/autocomplete.js
new file mode 100644
index 00000000..6f9cec6e
--- /dev/null
+++ b/app/assets/javascripts/modules/autocomplete.js
@@ -0,0 +1,22 @@
+$(document).on('ready page:load', function() {
+ var terms = new Bloodhound({
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ remote: {
+ url: '/suggest?q=%QUERY'
+ }
+ });
+
+ terms.initialize();
+
+ $('input.search_q').typeahead({
+ hint: true,
+ highlight: true,
+ minLength: 2
+ },
+ {
+ name: 'terms',
+ displayKey: 'term',
+ source: terms.ttAdapter()
+ });
+});
diff --git a/app/assets/stylesheets/earthworks.css.scss b/app/assets/stylesheets/earthworks.css.scss
index fdfb500f..f41798e9 100644
--- a/app/assets/stylesheets/earthworks.css.scss
+++ b/app/assets/stylesheets/earthworks.css.scss
@@ -10,6 +10,7 @@
@import 'modules/show';
@import 'modules/sul_footer';
@import 'modules/top_navbar';
+@import 'modules/typeahead';
@import 'modules/zero_results';
diff --git a/app/assets/stylesheets/modules/typeahead.scss b/app/assets/stylesheets/modules/typeahead.scss
new file mode 100644
index 00000000..8cc7694d
--- /dev/null
+++ b/app/assets/stylesheets/modules/typeahead.scss
@@ -0,0 +1,31 @@
+.twitter-typeahead {
+ float: left;
+ width: 100%;
+ z-index: 10000;
+
+ .tt-input.form-control {
+ width: 100%;
+ }
+
+ .tt-hint.form-control {
+ width: 100%;
+ }
+
+ .tt-dropdown-menu {
+ @extend .dropdown-menu;
+ font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;
+
+ width: 100%;
+
+ .tt-suggestion p{
+ font-size: 14px;
+ padding-left: 10px;
+ }
+
+ .tt-cursor {
+ background-color: $dropdown-link-hover-bg;
+ color: $dropdown-link-hover-color;
+ text-decoration: none;
+ }
+ }
+}
diff --git a/app/controllers/suggest_controller.rb b/app/controllers/suggest_controller.rb
new file mode 100644
index 00000000..9bff7013
--- /dev/null
+++ b/app/controllers/suggest_controller.rb
@@ -0,0 +1,3 @@
+class SuggestController < ApplicationController
+ include Earthworks::Suggest
+end
diff --git a/config/application.rb b/config/application.rb
index b14b075c..7e1a59f8 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -11,6 +11,9 @@ class Application < Rails::Application
config.application_name = 'EarthWorks'
require 'rights_metadata'
+ require 'suggest/response'
+ require 'suggest/search_helper'
+ require 'suggest'
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
diff --git a/config/blacklight.yml b/config/blacklight.yml
index 3977b3bf..60636aa2 100644
--- a/config/blacklight.yml
+++ b/config/blacklight.yml
@@ -11,8 +11,8 @@
# how to start up solr, generally for automated testing.
development:
- url: <%= ENV['SOLR_URL'] || "http://127.0.0.1:8983/solr" %>
+ url: <%= ENV['SOLR_URL'] || "http://127.0.0.1:8983/solr/blacklight-core" %>
test: &test
- url: <%= ENV['TEST_SOLR_URL'] || "http://127.0.0.1:8888/solr" %>
+ url: <%= ENV['TEST_SOLR_URL'] || "http://127.0.0.1:8888/solr/blacklight-core" %>
production:
- url: <%= ENV['SOLR_URL'] || "http://127.0.0.1:8983/solr" %>
+ url: <%= ENV['SOLR_URL'] || "http://127.0.0.1:8983/solr/blacklight-core" %>
diff --git a/config/routes.rb b/config/routes.rb
index 4e004c5b..bb0c42a8 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -7,6 +7,8 @@
match 'users/auth/webauth/logout' => 'devise/sessions#destroy', :as => :destroy_user_session, :via => Devise.mappings[:user].sign_out_via
end
+ resources :suggest, only: :index, defaults: { format: 'json' }
+
resource :feedback_form, path: 'feedback', only: [:new, :create]
get 'feedback' => 'feedback_forms#new'
# The priority is based upon order of creation: first created -> highest priority.
diff --git a/config/solr_configs/schema.xml b/config/solr_configs/schema.xml
index 0234e0cd..ced33f7b 100644
--- a/config/solr_configs/schema.xml
+++ b/config/solr_configs/schema.xml
@@ -35,6 +35,10 @@
+
+
+
+
@@ -154,5 +177,20 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/solr_configs/solrconfig.xml b/config/solr_configs/solrconfig.xml
index 3e93169c..6434f3f7 100644
--- a/config/solr_configs/solrconfig.xml
+++ b/config/solr_configs/solrconfig.xml
@@ -147,7 +147,13 @@
dc_subject_sm
layer_geom_type_s
solr_year_i
+
+ true
+
+
+ spellcheck
+
@@ -177,4 +183,51 @@
*:*
-
\ No newline at end of file
+
+
+
+
+ default
+ spell
+ solr.DirectSolrSpellChecker
+
+ internal
+
+ 0.5
+
+ 2
+
+ 1
+
+ 5
+
+ 4
+
+ 0.01
+
+
+
+
+
+
+ mySuggester
+ FuzzyLookupFactory
+ textSuggest
+ true
+ suggest
+
+
+
+
+
+ true
+ 5
+ mySuggester
+
+
+ suggest
+
+
+
diff --git a/lib/suggest.rb b/lib/suggest.rb
new file mode 100644
index 00000000..6f816f58
--- /dev/null
+++ b/lib/suggest.rb
@@ -0,0 +1,17 @@
+module Earthworks
+ module Suggest
+ extend ActiveSupport::Concern
+ include Suggest::SearchHelper
+
+ ##
+ # Get suggestion results from the Solr index
+ def index
+ @response = get_suggestions params
+ respond_to do |format|
+ format.json do
+ render json: @response.suggestions
+ end
+ end
+ end
+ end
+end
diff --git a/lib/suggest/response.rb b/lib/suggest/response.rb
new file mode 100644
index 00000000..6eed93df
--- /dev/null
+++ b/lib/suggest/response.rb
@@ -0,0 +1,24 @@
+module Earthworks
+ module Suggest
+ class Response
+ attr_reader :response, :request_params
+
+ ##
+ # Creates a suggest response
+ # @param [RSolr::HashWithResponse] response
+ # @param [Hash] request_params
+ def initialize(response, request_params)
+ @response = response
+ @request_params = request_params
+ end
+
+ ##
+ # Trys the suggestor response to return suggestions if they are
+ # present
+ # @return [Array]
+ def suggestions
+ response.try(:[], 'suggest').try(:[], 'mySuggester').try(:[], request_params[:q]).try(:[], 'suggestions') || []
+ end
+ end
+ end
+end
diff --git a/lib/suggest/search_helper.rb b/lib/suggest/search_helper.rb
new file mode 100644
index 00000000..e54c7dec
--- /dev/null
+++ b/lib/suggest/search_helper.rb
@@ -0,0 +1,26 @@
+module Earthworks
+ module Suggest
+ module SearchHelper
+ extend ActiveSupport::Concern
+ include Blacklight::SearchHelper
+
+ ##
+ # For now, only use the q parameter to create a
+ # Earthworks::Suggest::Response
+ # @param [Hash] params
+ # @return [Earthworks::Suggest::Response]
+ def get_suggestions(params)
+ request_params = { q: params[:q] }
+ Earthworks::Suggest::Response.new suggest_results(request_params), request_params
+ end
+
+ ##
+ # Query the suggest handler using RSolr::Client::send_and_receive
+ # @param [Hash] request_params
+ # @return [RSolr::HashWithResponse]
+ def suggest_results(request_params)
+ repository.connection.send_and_receive('suggest', params: request_params)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/suggest_controller_spec.rb b/spec/controllers/suggest_controller_spec.rb
new file mode 100644
index 00000000..7dd40097
--- /dev/null
+++ b/spec/controllers/suggest_controller_spec.rb
@@ -0,0 +1,20 @@
+require 'rails_helper'
+
+describe SuggestController do
+ describe 'GET index' do
+ it 'assigns @response' do
+ get :index, format: 'json'
+ expect(assigns(:response)).to be_an Earthworks::Suggest::Response
+ end
+ it 'renders json' do
+ get :index, format: 'json'
+ expect(response.body).to eq [].to_json
+ end
+ it 'returns suggestions' do
+ get :index, format: 'json', q: 'st'
+ json = JSON.parse(response.body)
+ expect(json.count).to eq 2
+ expect(json.first['term']).to eq 'stanford'
+ end
+ end
+end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
new file mode 100644
index 00000000..410a1294
--- /dev/null
+++ b/spec/features/search_spec.rb
@@ -0,0 +1,12 @@
+require 'rails_helper'
+
+feature 'Search' do
+ feature 'spelling suggestions' do
+ scenario 'are turned on' do
+ visit root_path
+ fill_in 'q', with: 'standford'
+ click_button 'search'
+ expect(page).to have_content 'Did you mean to type:'
+ end
+ end
+end
diff --git a/spec/lib/suggest/response_spec.rb b/spec/lib/suggest/response_spec.rb
new file mode 100644
index 00000000..7ce042c0
--- /dev/null
+++ b/spec/lib/suggest/response_spec.rb
@@ -0,0 +1,48 @@
+require 'rails_helper'
+
+describe Earthworks::Suggest::Response do
+ let(:empty_response) { Earthworks::Suggest::Response.new({}, q: 'hello') }
+ let(:response) do
+ Earthworks::Suggest::Response.new(
+ {
+ 'responseHeader' => {
+ 'status' => 0,
+ 'QTime' => 42
+ },
+ 'suggest' => {
+ 'mySuggester' => {
+ 'st' => {
+ 'numFound' => 2,
+ 'suggestions' => [
+ {
+ 'term' => 'stanford',
+ 'weight' => 3,
+ 'payload' => ''
+ },
+ {
+ 'term' => 'statistics',
+ 'weight' => 1,
+ 'payload' => ''
+ }
+ ]
+ }
+ }
+ }
+ },
+ q: 'st'
+ )
+ end
+
+ describe '#initialize' do
+ it 'creates a Earthworks::Suggest::Response' do
+ expect(empty_response).to be_an Earthworks::Suggest::Response
+ end
+ end
+ describe '#suggestions' do
+ it 'returns an array of suggestions' do
+ expect(response.suggestions).to be_an Array
+ expect(response.suggestions.count).to eq 2
+ expect(response.suggestions.first['term']).to eq 'stanford'
+ end
+ end
+end
diff --git a/spec/lib/suggest/search_helper_spec.rb b/spec/lib/suggest/search_helper_spec.rb
new file mode 100644
index 00000000..855f0ff1
--- /dev/null
+++ b/spec/lib/suggest/search_helper_spec.rb
@@ -0,0 +1,44 @@
+require 'rails_helper'
+
+describe Earthworks::Suggest::SearchHelper do
+
+ class SearchHelperTestClass
+ include Earthworks::Suggest::SearchHelper
+
+ attr_accessor :blacklight_config
+ attr_accessor :repository
+
+ def initialize blacklight_config, conn
+ self.blacklight_config = blacklight_config
+ self.repository = Blacklight::SolrRepository.new(blacklight_config)
+ self.repository.connection = conn
+ end
+
+ def params
+ {}
+ end
+ end
+
+ subject { SearchHelperTestClass.new blacklight_config, blacklight_solr }
+
+ let(:blacklight_config) { Blacklight::Configuration.new }
+ let(:copy_of_catalog_config) { ::CatalogController.blacklight_config.deep_copy }
+ let(:blacklight_solr) { RSolr.connect(Blacklight.connection_config) }
+
+
+
+ describe '#get_suggestions' do
+ it 'returns a Earthworks::Suggest::Response' do
+ expect(subject.get_suggestions q: 'test').to be_an Earthworks::Suggest::Response
+ end
+ end
+ describe '#suggest_results' do
+ it 'queries the suggest handler with params' do
+ allow(blacklight_solr).to receive(:get) do |path, params|
+ expect(path).to eq 'suggest'
+ expect(params).to eq params: { q: 'st' }
+ end
+ subject.query_solr q: 'st'
+ end
+ end
+end