lib/blacklight/search_state/filter_field.rb
# frozen_string_literal: true
module Blacklight
class SearchState
# Modeling access to filter query parameters
class FilterField
MISSING = { missing: true }.freeze
# @!attribute config
# @return [Blacklight::Configuration::FacetField]
# @!attribute search_state
# @return [Blacklight::SearchState]
# @!attribute param
# @return [String,Symbol]
# @!attribute inclusive_param
# @return [String,Symbol]
attr_reader :config, :search_state, :filters_key, :inclusive_filters_key
# @return [String,Symbol]
delegate :key, to: :config
# @param [Blacklight::Configuration::FacetField] config
# @param [Blacklight::SearchState] search_state
def initialize(config, search_state)
@config = config
@search_state = search_state
@filters_key = :f
@inclusive_filters_key = :f_inclusive
end
# @param [String,#value] item a filter item to add to the url
# @return [Blacklight::SearchState] new state
def add(item)
new_state = search_state.reset_search
if item.try(:missing)
# if this is a 'missing' facet value, the :fq is only for backwards compatibility
elsif item.respond_to?(:fq)
Array(item.fq).each do |f, v|
new_state = new_state.filter(f).add(v)
end
end
return new_state.filter(item.field).add(item) if item.respond_to?(:field) && item.field != key
url_key = key
params = new_state.params
param = filters_key
value = as_url_parameter(item)
if value == Blacklight::SearchState::FilterField::MISSING
url_key = "-#{key}"
value = Blacklight::Engine.config.blacklight.facet_missing_param
end
param = inclusive_filters_key if value.is_a?(Array)
# value could be a string
params[param] = (params[param] || {}).dup
if value.is_a? Array
params[param][url_key] = value
elsif config.single
params[param][url_key] = [value]
else
params[param][url_key] = Array(params[param][url_key] || []).dup
params[param][url_key].push(value)
end
new_state.reset(params)
end
# @param [String,#value] item a filter to remove from the url
# @return [Blacklight::SearchState] new state
def remove(item)
new_state = search_state.reset_search
return new_state.filter(item.field).remove(item) if item.respond_to?(:field) && item.field != key
url_key = config.key
params = new_state.params
param = filters_key
value = as_url_parameter(item)
if value == Blacklight::SearchState::FilterField::MISSING
url_key = "-#{key}"
value = Blacklight::Engine.config.blacklight.facet_missing_param
end
param = inclusive_filters_key if value.is_a?(Array)
# need to dup the facet values too,
# if the values aren't dup'd, then the values
# from the session will get remove in the show view...
params[param] = (params[param] || {}).dup
params[param][url_key] = (params[param][url_key] || []).dup
collection = params[param][url_key]
params[param][url_key] = collection - Array(value)
params[param].delete(url_key) if params[param][url_key].empty?
params.delete(param) if params[param].empty?
new_state.reset(params)
end
# @return [Array] an array of applied filters
def values(except: [])
params = search_state.params
return [] if params.blank?
f = except.include?(:filters) ? [] : [params.dig(filters_key, key)].flatten.compact
f_inclusive = [params.dig(:f_inclusive, key)] unless params.dig(inclusive_filters_key, key).blank? || except.include?(:inclusive_filters)
f_missing = [Blacklight::SearchState::FilterField::MISSING] if params.dig(filters_key, "-#{key}")&.any? { |v| v == Blacklight::Engine.config.blacklight.facet_missing_param }
f_missing = [] if except.include?(:missing)
f + (f_inclusive || []) + (f_missing || [])
end
delegate :any?, to: :values
# Appease rubocop rules by implementing #each_value
def each_value(except: [], &block)
values(except: except).each(&block)
end
# @param [String,#value] item a filter to remove from the url
# @return [Boolean] whether the provided filter is currently applied/selected
def include?(item)
return search_state.filter(item.field).selected?(item) if item.respond_to?(:field) && item.field != key
value = as_url_parameter(item)
params = search_state.params
case value
when Array
(params.dig(inclusive_filters_key, key) || []).to_set == value.to_set
when Blacklight::SearchState::FilterField::MISSING
(params.dig(filters_key, "-#{key}") || []).include?(Blacklight::Engine.config.blacklight.facet_missing_param)
else
(params.dig(filters_key, key) || []).include?(value)
end
end
def permitted_params
if config.pivot
{
filters_key => config.pivot.each_with_object({}) { |key, filter| filter.merge(key => [], "-#{key}" => []) },
inclusive_filters_key => config.pivot.each_with_object({}) { |key, filter| filter.merge(key => []) }
}
else
{
filters_key => { config.key => [], "-#{config.key}" => [] },
inclusive_filters_key => { config.key => [] }
}
end
end
private
# TODO: this code is duplicated in Blacklight::FacetsHelperBehavior
def as_url_parameter(item)
if item.respond_to?(:missing) && item.missing
Blacklight::SearchState::FilterField::MISSING
elsif item.respond_to? :value
item.value
else
item
end
end
end
end
end