diff --git a/app/assets/stylesheets/blacklight/_facets.scss b/app/assets/stylesheets/blacklight/_facets.scss
index 45bc38def3..c40bbcc8f4 100644
--- a/app/assets/stylesheets/blacklight/_facets.scss
+++ b/app/assets/stylesheets/blacklight/_facets.scss
@@ -150,10 +150,26 @@
.pivot-facet {
@extend .list-unstyled;
+ @extend .py-1;
+ @extend .px-4;
+}
+
+.facet-leaf-node {
+ margin-left: 1rem;
+ padding-right: 1rem;
+ margin-top: -1.5rem;
+}
- ul, .pivot-facet {
- @extend .list-unstyled;
- @extend .py-1;
- @extend .px-3;
+.facet-toggle-handle {
+ margin: 0;
+ margin-left: -5px;
+ padding: 0;
+
+ &.collapsed {
+ .show { display: block; }
+ .hide { display: none; }
}
+
+ .show { display: none; }
+ .hide { display: block; }
}
diff --git a/app/components/blacklight/facet_item_pivot_component.rb b/app/components/blacklight/facet_item_pivot_component.rb
new file mode 100644
index 0000000000..b0fac17219
--- /dev/null
+++ b/app/components/blacklight/facet_item_pivot_component.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+module Blacklight
+ # Render facet items and any subtree
+ class FacetItemPivotComponent < ::ViewComponent::Base
+ # Somewhat arbitrary number; the only important thing is that
+ # it is bigger than the number of leaf nodes in any collapsing
+ # pivot facet on the page.
+ ID_COUNTER_MAX = 2**20 - 1
+
+ # Mint a (sufficiently) unique identifier, so we can associate
+ # the expand/collapse control with labels
+ def self.mint_id
+ @id_counter = ((@id_counter || 0) + 1) % ID_COUNTER_MAX
+
+ # We convert the ID to hex for markup compactness
+ @id_counter.to_s(16)
+ end
+
+ with_collection_parameter :facet_item
+
+ def initialize(facet_item:, wrapping_element: 'li', suppress_link: false, collapsing: nil)
+ @facet_item = facet_item
+ @wrapping_element = wrapping_element
+ @suppress_link = suppress_link
+ @collapsing = collapsing.nil? ? facet_item.facet_config.collapsing : collapsing
+ @icons = { show: '⊞', hide: '⊟' }.merge(facet_item.facet_config.icons || {})
+ end
+
+ def call
+ facet = Blacklight::FacetItemComponent.new(facet_item: @facet_item, wrapping_element: nil, suppress_link: @suppress_link)
+
+ id = "h-#{self.class.mint_id}" if @collapsing && has_items?
+
+ content_tag @wrapping_element, role: 'treeitem' do
+ concat facet_toggle_button(id) if has_items? && @collapsing
+ concat content_tag('span', render_component(facet), class: "facet-values #{'facet-leaf-node' if has_items? && @collapsing}", id: id && "#{id}_label")
+
+ if has_items?
+ concat(content_tag('ul', class: "pivot-facet list-unstyled #{'collapse' if @collapsing}", id: id, role: 'group') do
+ render_component(
+ self.class.with_collection(
+ @facet_item.items.map { |i| facet_item_presenter(i) }
+ )
+ )
+ end)
+ end
+ end
+ end
+
+ private
+
+ def has_items?
+ @facet_item.items.present?
+ end
+
+ def facet_toggle_button(id)
+ content_tag 'button', class: 'btn facet-toggle-handle collapsed',
+ data: { toggle: 'collapse', target: "##{id}" },
+ aria: { expanded: false, controls: id, describedby: "#{id}_label" } do
+ concat toggle_icon(:show)
+ concat toggle_icon(:hide)
+ end
+ end
+
+ def toggle_icon(type)
+ content_tag 'span', class: type do
+ concat @icons[type]
+ concat content_tag('span', t(type, scope: 'blacklight.search.facets.pivot'), class: 'sr-only')
+ end
+ end
+
+ # This is a little convoluted in Blacklight 7 in order to maintain backwards-compat
+ # with overrides of deprecated helpers. In 8.x, we can just call Component#render_in
+ # and call it a day
+ def render_component(component)
+ @view_context.render(component)
+ end
+
+ def facet_item_presenter(facet_item)
+ Blacklight::FacetItemPresenter.new(facet_item, @facet_item.facet_config, @view_context, @facet_item.facet_field, @facet_item.search_state)
+ end
+ end
+end
diff --git a/app/helpers/blacklight/facets_helper_behavior.rb b/app/helpers/blacklight/facets_helper_behavior.rb
index e4f3e8665d..e79133e9a5 100644
--- a/app/helpers/blacklight/facets_helper_behavior.rb
+++ b/app/helpers/blacklight/facets_helper_behavior.rb
@@ -81,7 +81,11 @@ def render_facet_limit(display_facet, options = {})
return unless should_render_facet?(display_facet, field_config)
end
options = options.dup
- options[:partial] ||= facet_partial_name(display_facet)
+
+ Deprecation.silence(Blacklight::FacetsHelperBehavior) do
+ options[:partial] ||= facet_partial_name(display_facet)
+ end
+
options[:layout] ||= "facet_layout" unless options.key?(:layout)
options[:locals] ||= {}
options[:locals][:field_name] ||= display_facet.name
@@ -97,13 +101,12 @@ def render_facet_limit(display_facet, options = {})
# to filter undesireable facet items so they don't appear in the UI
def render_facet_limit_list(paginator, facet_field, wrapping_element = :li)
facet_config ||= facet_configuration_for_field(facet_field)
- component = facet_config.fetch(:item_component, Blacklight::FacetItemComponent)
collection = paginator.items.map do |item|
facet_item_presenter(facet_config, item, facet_field)
end
- render(component.with_collection(collection, wrapping_element: wrapping_element))
+ render(facet_item_component_class(facet_config).with_collection(collection, wrapping_element: wrapping_element))
end
deprecation_deprecate :render_facet_limit_list
@@ -288,8 +291,12 @@ def facet_item_presenter(facet_config, facet_item, facet_field)
end
def facet_item_component(facet_config, facet_item, facet_field, **args)
- component = facet_config.fetch(:item_component, Blacklight::FacetItemComponent)
- component.new(facet_item: facet_item_presenter(facet_config, facet_item, facet_field), **args).with_view_context(self)
+ facet_item_component_class(facet_config).new(facet_item: facet_item_presenter(facet_config, facet_item, facet_field), **args).with_view_context(self)
+ end
+
+ def facet_item_component_class(facet_config)
+ default_component = facet_config.pivot ? Blacklight::FacetItemPivotComponent : Blacklight::FacetItemComponent
+ facet_config.fetch(:item_component, default_component)
end
# We can't use .deprecation_deprecate here, because the new components need to
diff --git a/app/presenters/blacklight/facet_item_presenter.rb b/app/presenters/blacklight/facet_item_presenter.rb
index 2d059dc7aa..66bb729639 100644
--- a/app/presenters/blacklight/facet_item_presenter.rb
+++ b/app/presenters/blacklight/facet_item_presenter.rb
@@ -4,7 +4,7 @@ module Blacklight
class FacetItemPresenter
attr_reader :facet_item, :facet_config, :view_context, :search_state, :facet_field
- delegate :hits, to: :facet_item
+ delegate :hits, :items, to: :facet_item
def initialize(facet_item, facet_config, view_context, facet_field, search_state = view_context.search_state)
@facet_item = facet_item
diff --git a/app/views/catalog/_facet_pivot.html.erb b/app/views/catalog/_facet_pivot.html.erb
index 77ac933467..212b89f5fa 100644
--- a/app/views/catalog/_facet_pivot.html.erb
+++ b/app/views/catalog/_facet_pivot.html.erb
@@ -1,18 +1,3 @@
-
- <% display_facet.items.each do |item| -%>
- -
-
- <% if facet_in_params?(field_name, item) %>
- <%= render_selected_facet_value(field_name, item) %>
- <% else %>
- <%= render_facet_value(field_name, item) %>
- <% end -%>
-
-
- <% unless item.items.blank? %>
- <%= render 'facet_pivot', display_facet: item, field_name: field_name %>
- <% end %>
-
- <% end %>
-
-
+<%= render(Blacklight::FacetFieldListComponent.new(
+ facet_field: facet_field_presenter(facet_field.merge(item_component: Blacklight::FacetItemPivotComponent), display_facet),
+ layout: false)) %>
diff --git a/config/locales/blacklight.ar.yml b/config/locales/blacklight.ar.yml
index ed73b27d97..77fade2e30 100644
--- a/config/locales/blacklight.ar.yml
+++ b/config/locales/blacklight.ar.yml
@@ -214,6 +214,9 @@ ar:
remove: '[إزالة]'
missing: "[غير موجود]"
all: الكل
+ pivot:
+ show: فتح
+ hide: "إغلاق"
group:
more: 'المزيد »'
filters:
diff --git a/config/locales/blacklight.de.yml b/config/locales/blacklight.de.yml
index a670b1eb0d..305f204dbb 100644
--- a/config/locales/blacklight.de.yml
+++ b/config/locales/blacklight.de.yml
@@ -194,6 +194,9 @@ de:
selected:
remove: '[entfernen]'
missing: [fehlt]
+ pivot:
+ show: Öffnen
+ hide: Schließen
group:
more: 'mehr »'
filters:
diff --git a/config/locales/blacklight.en.yml b/config/locales/blacklight.en.yml
index 982ef802d6..805d20360e 100644
--- a/config/locales/blacklight.en.yml
+++ b/config/locales/blacklight.en.yml
@@ -194,6 +194,9 @@ en:
remove: '[remove]'
missing: "[Missing]"
all: All
+ pivot:
+ show: Show
+ hide: Hide
group:
more: 'more »'
filters:
diff --git a/config/locales/blacklight.es.yml b/config/locales/blacklight.es.yml
index b50e7f67da..c2d44b3a19 100644
--- a/config/locales/blacklight.es.yml
+++ b/config/locales/blacklight.es.yml
@@ -194,6 +194,9 @@ es:
selected:
remove: '[borrar]'
missing: '[Falta]'
+ pivot:
+ show: Abierto
+ hide: Cerrar
group:
more: 'más »'
filters:
diff --git a/config/locales/blacklight.fr.yml b/config/locales/blacklight.fr.yml
index 41a1d830af..1be66c0487 100755
--- a/config/locales/blacklight.fr.yml
+++ b/config/locales/blacklight.fr.yml
@@ -197,6 +197,9 @@ fr:
selected:
remove: '[ X ]'
missing: '[manquante]'
+ pivot:
+ show: Ouvrir
+ hide: Fermer
group:
more: 'plus »'
filters:
diff --git a/config/locales/blacklight.hu.yml b/config/locales/blacklight.hu.yml
index 11cbf1cd75..8d3b513a4d 100644
--- a/config/locales/blacklight.hu.yml
+++ b/config/locales/blacklight.hu.yml
@@ -194,6 +194,9 @@ hu:
selected:
remove: '[eltávolítás]'
missing: "[Hiányzó]"
+ pivot:
+ show: Megnyitás
+ hide: Bezárás
group:
more: 'több »'
filters:
diff --git a/config/locales/blacklight.it.yml b/config/locales/blacklight.it.yml
index a45a88cb77..00adec69ee 100644
--- a/config/locales/blacklight.it.yml
+++ b/config/locales/blacklight.it.yml
@@ -194,6 +194,9 @@ it:
selected:
remove: '[cancella]'
missing: [Mancante]
+ pivot:
+ show: Apri
+ hide: Chiudi
group:
more: 'altri »'
filters:
diff --git a/config/locales/blacklight.nl.yml b/config/locales/blacklight.nl.yml
index 6e7680c781..3c2f85ea3f 100644
--- a/config/locales/blacklight.nl.yml
+++ b/config/locales/blacklight.nl.yml
@@ -194,6 +194,9 @@ nl:
selected:
remove: '[verwijder]'
missing: "[Ontbrekend]"
+ pivot:
+ show: Openen
+ hide: Sluiten
group:
more: 'meer »'
filters:
diff --git a/config/locales/blacklight.pt-BR.yml b/config/locales/blacklight.pt-BR.yml
index 63fd50563c..6cf4de202c 100644
--- a/config/locales/blacklight.pt-BR.yml
+++ b/config/locales/blacklight.pt-BR.yml
@@ -195,6 +195,9 @@ pt-BR:
selected:
remove: '[remover]'
missing: [Ausência]
+ pivot:
+ show: Abrir
+ hide: Fechar
group:
more: 'mais »'
filters:
diff --git a/config/locales/blacklight.sq.yml b/config/locales/blacklight.sq.yml
index 6a665db78a..0e037408a5 100644
--- a/config/locales/blacklight.sq.yml
+++ b/config/locales/blacklight.sq.yml
@@ -194,6 +194,9 @@ sq:
selected:
remove: '[fshije]'
missing: "[Mungon]"
+ pivot:
+ show: Hape
+ hide: "Mbylle"
group:
more: 'më shumë »'
filters:
diff --git a/config/locales/blacklight.zh.yml b/config/locales/blacklight.zh.yml
index 6cd6ac54d2..973fb71987 100644
--- a/config/locales/blacklight.zh.yml
+++ b/config/locales/blacklight.zh.yml
@@ -194,6 +194,9 @@ zh:
selected:
remove: '[删除]'
missing: "[未找到]"
+ pivot:
+ show: 打开
+ hide: 关
group:
more: '更多 »'
filters:
diff --git a/lib/generators/blacklight/templates/catalog_controller.rb b/lib/generators/blacklight/templates/catalog_controller.rb
index 3d14ecb27f..d947b7e730 100644
--- a/lib/generators/blacklight/templates/catalog_controller.rb
+++ b/lib/generators/blacklight/templates/catalog_controller.rb
@@ -84,7 +84,7 @@ class <%= controller_name.classify %>Controller < ApplicationController
config.add_facet_field 'subject_geo_ssim', label: 'Region'
config.add_facet_field 'subject_era_ssim', label: 'Era'
- config.add_facet_field 'example_pivot_field', label: 'Pivot Field', :pivot => ['format', 'language_ssim']
+ config.add_facet_field 'example_pivot_field', label: 'Pivot Field', pivot: ['format', 'language_ssim'], collapsing: true
config.add_facet_field 'example_query_facet_field', label: 'Publish Date', :query => {
:years_5 => { label: 'within 5 Years', fq: "pub_date_ssim:[#{Time.zone.now.year - 5 } TO *]" },
diff --git a/spec/components/blacklight/facet_item_pivot_component_spec.rb b/spec/components/blacklight/facet_item_pivot_component_spec.rb
new file mode 100644
index 0000000000..7201d8801b
--- /dev/null
+++ b/spec/components/blacklight/facet_item_pivot_component_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Blacklight::FacetItemPivotComponent, type: :component do
+ subject(:render) do
+ render_inline(described_class.new(facet_item: facet_item))
+ end
+
+ let(:rendered) do
+ Capybara::Node::Simple.new(render)
+ end
+
+ let(:search_state) do
+ Blacklight::SearchState.new({}, Blacklight::Configuration.new)
+ end
+
+ let(:facet_item) do
+ instance_double(
+ Blacklight::FacetItemPresenter,
+ facet_config: Blacklight::Configuration::FacetField.new(key: 'z'),
+ facet_field: 'z',
+ label: 'x',
+ hits: 10,
+ href: '/catalog?f[z]=x',
+ selected?: false,
+ search_state: search_state,
+ items: [OpenStruct.new(value: 'x:1', hits: 5)]
+ )
+ end
+
+ it 'links to the facet and shows the number of hits' do
+ expect(rendered).to have_selector 'li'
+ expect(rendered).to have_link 'x', href: '/catalog?f[z]=x'
+ expect(rendered).to have_selector '.facet-count', text: '10'
+ end
+
+ it 'has the facet hierarchy' do
+ puts render
+ expect(rendered).to have_selector 'li ul.pivot-facet'
+ expect(rendered).to have_link 'x:1', href: /f%5Bz%5D%5B%5D=x%3A1/
+ end
+
+ context 'with a selected facet' do
+ let(:facet_item) do
+ instance_double(
+ Blacklight::FacetItemPresenter,
+ facet_config: Blacklight::Configuration::FacetField.new,
+ facet_field: 'z',
+ label: 'x',
+ hits: 10,
+ href: '/catalog',
+ selected?: true,
+ search_state: search_state,
+ items: []
+ )
+ end
+
+ it 'links to the facet and shows the number of hits' do
+ expect(rendered).to have_selector 'li'
+ expect(rendered).to have_selector '.selected', text: 'x'
+ expect(rendered).to have_link '[remove]', href: '/catalog'
+ expect(rendered).to have_selector '.selected.facet-count', text: '10'
+ end
+ end
+end
diff --git a/spec/features/facets_spec.rb b/spec/features/facets_spec.rb
index 4f948848e3..6c9f161dc0 100644
--- a/spec/features/facets_spec.rb
+++ b/spec/features/facets_spec.rb
@@ -67,6 +67,26 @@
expect(page).to have_css('#facet-format', visible: true) # assert that it didn't re-collapse
end
+ it 'is able to expand pivot facets when javascript is enabled', js: true do
+ visit root_path
+
+ within('#facets .facets-header') do
+ page.find('button.navbar-toggler').click
+ end
+
+ page.find('h3.facet-field-heading button', text: 'Pivot Field').click
+
+ within '#facet-example_pivot_field' do
+ expect(page).to have_css('.facet-leaf-node', text: 'Book 30')
+ expect(page).not_to have_css('.facet-select', text: 'Tibetan')
+ page.find('.facet-toggle-handle').click
+ click_link 'Tibetan'
+ end
+
+ expect(page).to have_css('.constraint-value', text: 'Format Book')
+ expect(page).to have_css('.constraint-value', text: 'Language Tibetan')
+ end
+
describe 'heading button focus with Firefox' do
before do
Capybara.current_driver = :selenium_headless
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 010b350fef..c35d522960 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -25,6 +25,7 @@
require 'webdrivers'
Capybara.javascript_driver = :selenium_chrome_headless
+Capybara.disable_animation = true
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.