Skip to content

Commit 2985a9b

Browse files
authored
Merge pull request #3369 from projectblacklight/backport-facet-suggest-to-8
Backport #3367 to 8.x (Add a way to filter facets in the facets "more" modal)
2 parents bdb675a + c63c7fb commit 2985a9b

31 files changed

+304
-5
lines changed

.rubocop.yml

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Metrics/BlockLength:
4343

4444
Metrics/ClassLength:
4545
Exclude:
46+
- "app/services/blacklight/search_service.rb"
4647
- "lib/blacklight/configuration.rb"
4748
- "lib/blacklight/search_builder.rb"
4849
- "lib/blacklight/search_state.rb"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<label for="facet_suggest_<%= facet.key %>">
2+
<%= I18n.t('blacklight.search.facets.suggest.label', field_label: presenter&.label) %>
3+
</label>
4+
<%= text_field_tag "facet_suggest_#{facet.key}",
5+
nil,
6+
class: "facet-suggest form-control",
7+
data: {facet_field: facet.key},
8+
placeholder: I18n.t('blacklight.search.form.search.placeholder')
9+
%>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# frozen_string_literal: true
2+
3+
module Blacklight
4+
module Search
5+
class FacetSuggestInput < Blacklight::Component
6+
def initialize(facet:, presenter:)
7+
@facet = facet
8+
@presenter = presenter
9+
end
10+
11+
private
12+
13+
attr_accessor :facet, :presenter
14+
15+
def render?
16+
facet&.suggest
17+
end
18+
end
19+
end
20+
end

app/controllers/concerns/blacklight/catalog.rb

+7-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ def facet
8383
@facet = blacklight_config.facet_fields[params[:id]]
8484
raise ActionController::RoutingError, 'Not Found' unless @facet
8585

86-
@response = search_service.facet_field_response(@facet.key)
86+
@response = if params[:query_fragment].present?
87+
search_service.facet_suggest_response(@facet.key, params[:query_fragment])
88+
else
89+
search_service.facet_field_response(@facet.key)
90+
end
8791
@display_facet = @response.aggregations[@facet.field]
8892

8993
@presenter = @facet.presenter.new(@facet, @display_facet, view_context)
@@ -92,6 +96,8 @@ def facet
9296
format.html do
9397
# Draw the partial for the "more" facet modal window:
9498
return render layout: false if request.xhr?
99+
# Only show the facet names and their values:
100+
return render 'facet_values', layout: false if params[:only_values]
95101
# Otherwise draw the facet selector for users who have javascript disabled.
96102
end
97103
format.json

app/javascript/blacklight/debounce.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Usage:
2+
// ```
3+
// const basicFunction = (entry) => console.log(entry)
4+
// const debounced = debounce(basicFunction("I should only be called once"));
5+
//
6+
// debounced // does NOT print to the screen because it is invoked again less than 200 milliseconds later
7+
// debounced // does print to the screen
8+
// ```
9+
export default function debounce(func, timeout = 200) {
10+
let timer;
11+
return (...args) => {
12+
clearTimeout(timer);
13+
timer = setTimeout(() => { func.apply(this, args); }, timeout);
14+
};
15+
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import debounce from "blacklight/debounce";
2+
3+
const FacetSuggest = async (e) => {
4+
if (e.target.matches('.facet-suggest')) {
5+
const queryFragment = e.target.value?.trim();
6+
const facetField = e.target.dataset.facetField;
7+
if (!facetField) { return; }
8+
9+
const urlToFetch = `/catalog/facet_suggest/${facetField}/${queryFragment}`
10+
const response = await fetch(urlToFetch);
11+
if (response.ok) {
12+
const blob = await response.blob()
13+
const text = await blob.text()
14+
15+
const facetArea = document.querySelector('.facet-extended-list');
16+
17+
if (text && facetArea) {
18+
facetArea.innerHTML = text
19+
}
20+
}
21+
}
22+
};
23+
24+
document.addEventListener('input', debounce(FacetSuggest));
25+
26+
export default FacetSuggest

app/javascript/blacklight/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import BookmarkToggle from 'blacklight/bookmark_toggle'
22
import ButtonFocus from 'blacklight/button_focus'
3+
import FacetSuggest from 'blacklight/facet_suggest'
34
import Modal from 'blacklight/modal'
45
import ModalForm from 'blacklight/modalForm'
56
import SearchContext from 'blacklight/search_context'
@@ -8,6 +9,7 @@ import Core from 'blacklight/core'
89
export default {
910
BookmarkToggle,
1011
ButtonFocus,
12+
FacetSuggest,
1113
Modal,
1214
ModalForm,
1315
SearchContext,

app/presenters/blacklight/facet_field_presenter.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ def in_advanced_search?
3131
end
3232

3333
def in_modal?
34-
search_state.params[:action] == "facet"
34+
modal_like_actions = %w[facet facet_suggest]
35+
modal_like_actions.include? search_state.params[:action]
3536
end
3637

3738
def modal_path

app/services/blacklight/search_service.rb

+5
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ def facet_field_response(facet_field, extra_controller_params = {})
6262
repository.search(query.merge(extra_controller_params))
6363
end
6464

65+
def facet_suggest_response(facet_field, facet_suggestion_query, extra_controller_params = {})
66+
query = search_builder.with(search_state).facet(facet_field).facet_suggestion_query(facet_suggestion_query)
67+
repository.search(query.merge(extra_controller_params))
68+
end
69+
6570
# Get the previous and next document from a search result
6671
# @return [Blacklight::Solr::Response, Array<Blacklight::SolrDocument>] the solr response and a list of the first and last document
6772
def previous_and_next_documents_for_search(index, request_params, extra_controller_params = {})

app/views/catalog/facet.html.erb

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<% end %>
77

88
<% component.with_title { facet_field_label(@facet.key) } %>
9+
<%= render Blacklight::Search::FacetSuggestInput.new(facet: @facet, presenter: @presenter) %>
910

1011
<%= render partial: 'facet_index_navigation' if @facet.index_range && @display_facet.index? %>
1112

app/views/catalog/facet_values.erb

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<%= render Blacklight::FacetComponent.new(display_facet: @display_facet,
2+
blacklight_config: blacklight_config,
3+
layout: false) %>

config/locales/blacklight.ar.yml

+3
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ ar:
111111
sort:
112112
count: ترتيب رقمي
113113
index: ترتيب أبجدي
114+
suggest:
115+
label: الفلتر %{field_label}
116+
placeholder: فلتر...
114117
title: تحديد نطاق البحث
115118
filters:
116119
label: "%{label}:"

config/locales/blacklight.de.yml

+3
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ de:
102102
sort:
103103
count: Numerisch ordnen
104104
index: A-Z Ordnen
105+
suggest:
106+
label: Filter %{field_label}
107+
placeholder: Filter...
105108
title: Suche beschränken
106109
filters:
107110
label: "%{label}:"

config/locales/blacklight.en.yml

+3
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ en:
120120
sort:
121121
count: Numerical Sort
122122
index: A-Z Sort
123+
suggest:
124+
label: "Filter %{field_label}"
125+
placeholder: Filter...
123126
title: Limit your search
124127
filters:
125128
label: "%{label}:"

config/locales/blacklight.es.yml

+3
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ es:
101101
sort:
102102
count: Ordenación numérica
103103
index: Ordenación A-Z
104+
suggest:
105+
label: Filtro %{field_label}
106+
placeholder: Filtrar...
104107
title: Limite su búsqueda
105108
filters:
106109
label: "%{label}:"

config/locales/blacklight.fr.yml

+3
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ fr:
101101
sort:
102102
count: Du + au - fréquent
103103
index: Tri de A à Z
104+
suggest:
105+
label: Filtre %{field_label}
106+
placeholder: Filtre...
104107
title: Limiter votre recherche
105108
filters:
106109
label: "%{label}:"

config/locales/blacklight.hu.yml

+3
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ hu:
9999
sort:
100100
count: Numerikus rendezés
101101
index: A-Z rendezés
102+
suggest:
103+
label: Szűrő %{field_label}
104+
placeholder: Szűrő...
102105
title: Szűrje tovább a keresését
103106
filters:
104107
label: "%{label}:"

config/locales/blacklight.it.yml

+3
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ it:
102102
sort:
103103
count: Ordina per numero
104104
index: Ordina A-Z
105+
suggest:
106+
label: Filtro %{field_label}
107+
placeholder: Filtro...
105108
title: Affina la ricerca
106109
filters:
107110
label: "%{label}:"

config/locales/blacklight.nl.yml

+3
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ nl:
9999
sort:
100100
count: Numeriek sorteren
101101
index: A-Z Sorteren
102+
suggest:
103+
label: Filteren %{field_label}
104+
placeholder: Filter...
102105
title: Verfijn uw zoekopdracht
103106
filters:
104107
label: "%{label}:"

config/locales/blacklight.pt-BR.yml

+3
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ pt-BR:
100100
sort:
101101
count: Ordenar por Número
102102
index: Ordem Alfabética A-Z
103+
suggest:
104+
label: Filtro %{field_label}
105+
placeholder: Filtro...
103106
title: Filtre sua busca
104107
filters:
105108
label: "%{label}:"

config/locales/blacklight.sq.yml

+3
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ sq:
9999
sort:
100100
count: Renditja numerike
101101
index: A-Z Renditja
102+
suggest:
103+
label: Filtri %{field_label}
104+
placeholder: Filtro...
102105
title: Kufizo këkimin
103106
filters:
104107
label: "%{label}:"

config/locales/blacklight.zh.yml

+3
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ zh:
9999
sort:
100100
count: 按数量排序
101101
index: 按字母排序
102+
suggest:
103+
label: 过滤器 %{field_label}
104+
placeholder: 筛选...
102105
title: 限定搜索
103106
filters:
104107
label: "%{label}:"

lib/blacklight/configuration.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ def initialized_default_configuration?
3535
end
3636
end
3737

38-
BASIC_SEARCH_PARAMETERS = [:q, :qt, :page, :per_page, :search_field, :sort, :controller, :action, :'facet.page', :'facet.prefix', :'facet.sort', :rows, :format, :view].freeze
38+
BASIC_SEARCH_PARAMETERS = [:q, :qt, :page, :per_page, :search_field, :sort, :controller, :action, :'facet.page', :'facet.prefix', :'facet.sort', :rows, :format, :view, :id, :facet_id,
39+
:query_fragment, :only_values].freeze
3940
ADVANCED_SEARCH_PARAMETERS = [{ clause: {} }, :op].freeze
4041

4142
# rubocop:disable Metrics/BlockLength

lib/blacklight/routes/searchable.rb

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def call(mapper, _options = {})
1818
mapper.get "opensearch"
1919
mapper.get 'suggest', as: 'suggest_index'
2020
mapper.get "facet/:id", action: 'facet', as: 'facet'
21+
mapper.get "facet_suggest/:id/(:query_fragment)", action: 'facet', as: 'facet_suggest', defaults: { only_values: true }
2122
end
2223
end
2324
end

lib/blacklight/search_builder.rb

+13
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,19 @@ def facet(value = nil)
224224
@facet
225225
end
226226

227+
def facet_suggestion_query=(value)
228+
params_will_change!
229+
@facet_suggestion_query = value
230+
end
231+
232+
def facet_suggestion_query(value = nil)
233+
if value
234+
self.facet_suggestion_query = value
235+
return self
236+
end
237+
@facet_suggestion_query
238+
end
239+
227240
# Decode the user provided 'sort' parameter into a sort string that can be
228241
# passed to the search. This sanitizes the input by ensuring only
229242
# configured search values are passed through to the search.

lib/blacklight/solr/search_builder_behavior.rb

+9-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ module SearchBuilderBehavior
1010
:add_query_to_solr, :add_facet_fq_to_solr,
1111
:add_facetting_to_solr, :add_solr_fields_to_query, :add_paging_to_solr,
1212
:add_sorting_to_solr, :add_group_config_to_solr,
13-
:add_facet_paging_to_solr, :add_adv_search_clauses,
13+
:add_facet_paging_to_solr, :add_facet_suggestion_parameters,
14+
:add_adv_search_clauses,
1415
:add_additional_filters
1516
]
1617
end
@@ -276,6 +277,13 @@ def add_facet_paging_to_solr(solr_params)
276277
solr_params[:"f.#{facet_config.field}.facet.prefix"] = prefix if prefix
277278
end
278279

280+
def add_facet_suggestion_parameters(solr_params)
281+
return if facet.blank? || facet_suggestion_query.blank?
282+
283+
solr_params[:'facet.contains'] = facet_suggestion_query[0..50]
284+
solr_params[:'facet.contains.ignoreCase'] = true
285+
end
286+
279287
def with_ex_local_param(ex, value)
280288
if ex
281289
"{!ex=#{ex}}#{value}"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
RSpec.describe Blacklight::Search::FacetSuggestInput, type: :component do
6+
let(:facet) { Blacklight::Configuration::FacetField.new key: 'language_facet', suggest: true }
7+
let(:presenter) { instance_double(Blacklight::FacetFieldPresenter) }
8+
9+
before do
10+
allow(presenter).to receive(:label).and_return 'Language'
11+
end
12+
13+
it 'has an input with the facet-suggest class, which the javascript needs to find it' do
14+
rendered = render_inline(described_class.new(facet: facet, presenter: presenter))
15+
expect(rendered.css("input.facet-suggest").count).to eq 1
16+
end
17+
18+
it 'has an input with the data-facet-field attribute, which the javascript needs to determine the correct query' do
19+
rendered = render_inline(described_class.new(facet: facet, presenter: presenter))
20+
expect(rendered.css('input[data-facet-field="language_facet"]').count).to eq 1
21+
end
22+
23+
it 'has a visible label that is associated with the input' do
24+
rendered = render_inline(described_class.new(facet: facet, presenter: presenter))
25+
label = rendered.css('label').first
26+
expect(label.text.strip).to eq 'Filter Language'
27+
28+
id_in_label_for = label.attribute('for').text
29+
expect(id_in_label_for).to eq('facet_suggest_language_facet')
30+
31+
expect(rendered.css('input').first.attribute('id').text).to eq id_in_label_for
32+
end
33+
34+
context 'when the facet is explicitly configured to suggest: false' do
35+
let(:facet) { Blacklight::Configuration::FacetField.new key: 'language_facet', suggest: false }
36+
37+
it 'does not display' do
38+
expect(render_inline(described_class.new(facet: facet, presenter: presenter)).to_s).to eq ''
39+
end
40+
end
41+
42+
context 'when the facet is not explicitly configured with a suggest key' do
43+
let(:facet) { Blacklight::Configuration::FacetField.new key: 'language_facet' }
44+
45+
it 'does not display' do
46+
expect(render_inline(described_class.new(facet: facet, presenter: presenter)).to_s).to eq ''
47+
end
48+
end
49+
end

0 commit comments

Comments
 (0)