Skip to content

Commit

Permalink
Componentize hierarchy rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
jcoyne committed Jul 8, 2020
1 parent 9af5776 commit 47a0e69
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 5 deletions.
18 changes: 18 additions & 0 deletions app/components/blacklight/hierarchy/facet_field_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<li class="<%= li_class %>" role="treeitem">
<%= helpers.facet_toggle_button(field_name, id) if subset.any? %>
<% if item.nil? %>
<%= key %>
<% elsif qfacet_selected? %>
<%= render Blacklight::Hierarchy::SelectedQfacetValueComponent.new(field_name: field_name, item: item) %>
<% else %>
<%= render Blacklight::Hierarchy::QfacetValueComponent.new(field_name: field_name, item: item, id: id) %>
<% end %>

<% unless subset.empty? %>
<ul role=\"group\">
<% subset.keys.sort.each do |subkey| %>
<%= render self.class.new(field_name: field_name, tree: subset[subkey], key: subkey) %>
<% end %>
</ul>
<% end %>
</li>
33 changes: 33 additions & 0 deletions app/components/blacklight/hierarchy/facet_field_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module Blacklight
module Hierarchy
class FacetFieldComponent < ::ViewComponent::Base
def initialize(field_name:, tree:, key:)
@field_name = field_name
@tree = tree
@key = key
@id = SecureRandom.uuid
end

attr_reader :field_name, :tree, :key, :id

def subset
@subset ||= tree.reject { |k, _v| !k.is_a?(String) }
end

def li_class
subset.empty? ? 'h-leaf' : 'h-node'
end

def item
tree[:_]
end

def qfacet_selected?
config = helpers.facet_configuration_for_field(field_name)
helpers.search_state.has_facet?(config, value: item.qvalue)
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
<% end %>
<% component.with(:body) do %>
<ul class="facet-hierarchy" role="tree">
<%= render_hierarchy %>
<% tree.keys.sort.collect do |key| %>
<%= render Blacklight::Hierarchy::FacetFieldComponent.new(field_name: @facet_field.facet_field.field, tree: tree[key], key: key) %>
<% end %>
</ul>
<% end %>
<% end %>
65 changes: 63 additions & 2 deletions app/components/blacklight/hierarchy/facet_field_list_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,69 @@
module Blacklight
module Hierarchy
class FacetFieldListComponent < Blacklight::FacetFieldListComponent
def render_hierarchy
helpers.render_hierarchy(@facet_field.facet_field)
DELIMETER = '_'

# @param [Blacklight::Configuration::FacetField] as defined in controller with config.add_facet_field (and with :partial => 'blacklight/hierarchy/facet_hierarchy')
# @return [String] html for the facet tree
def tree
@tree ||= begin
facet_tree_for_prefix = facet_tree
facet_tree_for_prefix ? facet_tree_for_prefix[field_name] : nil
end
end

def field_name
@facet_field.facet_field.field
end

# @return [String] a key to access the rest of the hierarchy tree, as defined in controller config.facet_display[:hierarchy] declaration.
# e.g. if you had this in controller:
# config.facet_display = {
# :hierarchy => {
# 'wf' => [['wps','wsp','swp'], ':'],
# 'callnum_top' => [['facet'], '/'],
# 'exploded_tag' => [['ssim'], ':']
# }
# }
# then possible hkey values would be 'wf', 'callnum_top', and 'exploded_tag'.
#
# the key in the :hierarchy hash is the "prefix" for the solr field with the hierarchy info. the value
# in the hash is a list, where the first element is a list of suffixes, and the second element is the delimiter
# used to break up the sections of hierarchical data in the solr field being read. when joined, the prefix and
# suffix should form the field name. so, for example, 'wf_wps', 'wf_wsp', 'wf_swp', 'callnum_top_facet', and
# 'exploded_tag_ssim' would be the solr fields with blacklight-hierarchy related configuration according to the
# hash above. ':' would be the delimiter used in all of those fields except for 'callnum_top_facet', which would
# use '/'. exploded_tag_ssim might contain values like ['Book', 'Book : Multi-Volume Work'], and callnum_top_facet
# might contain values like ['LB', 'LB/2395', 'LB/2395/.C65', 'LB/2395/.C65/1991'].
# note: the suffixes (e.g. 'ssim' for 'exploded_tag' in the above example) can't have underscores, otherwise things break.
def prefix
@prefix ||= field_name.gsub("#{DELIMETER}#{field_name.split(/#{DELIMETER}/).last}", '')
end


delegate :blacklight_config, to: :helpers

def facet_tree
@facet_tree ||= {}
return @facet_tree[prefix] unless @facet_tree[prefix].nil?
return @facet_tree[prefix] unless blacklight_config.facet_display[:hierarchy] && blacklight_config.facet_display[:hierarchy][prefix]
@facet_tree[prefix] = {}
facet_config = blacklight_config.facet_display[:hierarchy][prefix]
split_regex = Regexp.new("\s*#{Regexp.escape(facet_config.length >= 2 ? facet_config[1] : ':')}\s*")
facet_config.first.each do |key|
# TODO: remove baked in notion of underscores being part of the blacklight facet field names
facet_field = [prefix, key].compact.join('_')
@facet_tree[prefix][facet_field] ||= {}
data = @facet_field.display_facet
next if data.nil?
data.items.each do |facet_item|
path = facet_item.value.split(split_regex)
loc = @facet_tree[prefix][facet_field]
loc = loc[path.shift] ||= {} while path.length > 0
loc[:_] = HierarchicalFacetItem.new(facet_item.value, facet_item.value.split(split_regex).last, facet_item.hits)
end
end
@facet_tree[prefix]
end
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= link_to_unless suppress_link, item.value, path_for_facet, id: id, class: 'facet_select' %> <%= render_facet_count %>
26 changes: 26 additions & 0 deletions app/components/blacklight/hierarchy/qfacet_value_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module Blacklight
module Hierarchy
class QfacetValueComponent < ::ViewComponent::Base
def initialize(field_name:, item:, id: nil, suppress_link: false)
@field_name = field_name
@item = item
@id = id
@suppress_link = suppress_link
end

attr_reader :field_name, :item, :id, :suppress_link

def path_for_facet
facet_config = helpers.facet_configuration_for_field(field_name)
Blacklight::FacetItemPresenter.new(item.qvalue, facet_config, helpers, field_name).href
end

def render_facet_count
classes = "facet-count"
content_tag("span", t('blacklight.search.facets.count', number: number_with_delimiter(item.hits)), class: classes)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<span class="selected"><%= render Blacklight::Hierarchy::QfacetValueComponent.new(field_name: field_name, item: item, suppress_link: true) %></span>
<%= link_to remove_href, class: 'remove' do %>
<span class="glyphicon glyphicon-remove"></span><span class="sr-only">[remove]</span>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module Blacklight
module Hierarchy
# Standard display of a SELECTED facet value, no link, special span with class, and 'remove' button.
class SelectedQfacetValueComponent < ::ViewComponent::Base
def initialize(field_name:, item:)
@field_name = field_name
@item = item
end

attr_reader :field_name, :item

def remove_href
helpers.search_action_path(helpers.search_state.remove_facet_params(field_name, item.qvalue))
end
end
end
end
16 changes: 14 additions & 2 deletions app/helpers/blacklight/hierarchy_helper.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'deprecation'

module Blacklight::HierarchyHelper
# Putting bare HTML strings in a helper sucks. But in this case, with a
# lot of recursive tree-walking going on, it's an order of magnitude faster
Expand Down Expand Up @@ -28,6 +30,7 @@ def render_facet_hierarchy_item(field_name, data, key)

%(<li class="#{li_class}" role="treeitem">#{li.html_safe}#{ul.html_safe}</li>).html_safe
end
deprecation_deprecate :render_facet_hierarchy_item

def qfacet_selected?(field_name, item)
config = facet_configuration_for_field(field_name)
Expand All @@ -48,13 +51,15 @@ def render_hierarchy(bl_facet_field, delim = '_')
render_facet_hierarchy_item(field_name, tree[key], key)
end.join("\n").html_safe
end
deprecation_deprecate :render_hierarchy

def render_qfacet_value(facet_solr_field, item, options = {})
id = options.delete(:id)
facet_config = facet_configuration_for_field(facet_solr_field)
path_for_facet = facet_item_presenter(facet_config, item.qvalue, facet_solr_field).href
(link_to_unless(options[:suppress_link], item.value, path_for_facet, id: id, class: 'facet_select') + ' ' + render_facet_count(item.hits)).html_safe
end
deprecation_deprecate :render_qfacet_value

# Standard display of a SELECTED facet value, no link, special span with class, and 'remove' button.
def render_selected_qfacet_value(facet_solr_field, item)
Expand All @@ -66,8 +71,7 @@ def render_selected_qfacet_value(facet_solr_field, item)
class: 'remove'
)
end

HierarchicalFacetItem = Struct.new :qvalue, :value, :hits
deprecation_deprecate :render_selected_qfacet_value

# @param [String] hkey - a key to access the rest of the hierarchy tree, as defined in controller config.facet_display[:hierarchy] declaration.
# e.g. if you had this in controller:
Expand Down Expand Up @@ -111,6 +115,7 @@ def facet_tree(hkey)
end
@facet_tree[hkey]
end
deprecation_deprecate :facet_tree

def facet_toggle_button(field_name, described_by)
aria_label = I18n.t(
Expand Down Expand Up @@ -139,11 +144,13 @@ def is_hierarchical?(field_name)
(prefix, order) = field_name.split(/_/, 2)
(list = blacklight_config.facet_display[:hierarchy][prefix]) && list.include?(order)
end
deprecation_deprecate :is_hierarchical?

def facet_order(prefix)
param_name = "#{prefix}_facet_order".to_sym
params[param_name] || blacklight_config.facet_display[:hierarchy][prefix].first
end
deprecation_deprecate :facet_order

def facet_after(prefix, order)
orders = blacklight_config.facet_display[:hierarchy][prefix]
Expand All @@ -156,6 +163,8 @@ def hide_facet?(field_name)
prefix = field_name.split(/_/).first
field_name != "#{prefix}_#{facet_order(prefix)}"
end
deprecation_deprecate :hide_facet?


# FIXME: remove baked in colon separator
def rotate_facet_value(val, from, to)
Expand All @@ -165,6 +174,7 @@ def rotate_facet_value(val, from, to)
return nil if new_values.include?(nil)
new_values.compact.join(':')
end
deprecation_deprecate :rotate_facet_value

# FIXME: remove baked in underscore separator in field name
def rotate_facet_params(prefix, from, to, p = params.dup)
Expand All @@ -180,6 +190,7 @@ def rotate_facet_params(prefix, from, to, p = params.dup)
p[:f].delete(to_field) if p[:f][to_field].empty?
p
end
deprecation_deprecate :rotate_facet_params

# FIXME: remove baked in underscore separator in field name
def render_facet_rotate(field_name)
Expand All @@ -191,4 +202,5 @@ def render_facet_rotate(field_name)
new_params["#{prefix}_facet_order"] = new_order
link_to image_tag('icons/rotate.png', title: new_order.upcase).html_safe, new_params, class: 'no-underline'
end
deprecation_deprecate :render_facet_rotate
end
1 change: 1 addition & 0 deletions app/models/hierarchical_facet_item.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
HierarchicalFacetItem = Struct.new :qvalue, :value, :hits
1 change: 1 addition & 0 deletions blacklight-hierarchy.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Gem::Specification.new do |s|
# A version of blacklight with view_component is required
s.add_dependency 'blacklight', '~> 7.9'
s.add_dependency 'rails', '>= 5.1', '< 7'
s.add_dependency 'deprecation'

s.add_development_dependency 'rsolr'
s.add_development_dependency 'rspec-rails'
Expand Down
1 change: 1 addition & 0 deletions spec/helpers/hierarchy_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
describe Blacklight::HierarchyHelper do
describe '#render_hierarchy' do
it 'should remove the _suffix from the field name' do
expect(Deprecation).to receive(:warn)
field = OpenStruct.new(field: 'the_field_name_facet')
expect(helper).to receive(:facet_tree).with('the_field_name').and_return({})
helper.render_hierarchy(field)
Expand Down

0 comments on commit 47a0e69

Please sign in to comment.