blacklight_advanced_search 2.2.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. data/Gemfile +8 -0
  2. data/{README.rdoc → README.md} +85 -59
  3. data/Rakefile +2 -2
  4. data/VERSION +1 -1
  5. data/app/assets/javascripts/blacklight_advanced_search.js +4 -4
  6. data/app/assets/stylesheets/blacklight_advanced_search/advanced_results.css +11 -31
  7. data/app/assets/stylesheets/blacklight_advanced_search/blacklight_advanced_search_styles.css.scss +42 -7
  8. data/app/controllers/blacklight_advanced_search/advanced_controller.rb +1 -3
  9. data/app/helpers/advanced_helper.rb +13 -9
  10. data/app/views/advanced/_advanced_search_facets.html.erb +15 -2
  11. data/app/views/advanced/_advanced_search_facets_as_select.html.erb +30 -0
  12. data/app/views/advanced/_advanced_search_fields.html.erb +5 -3
  13. data/app/views/advanced/_advanced_search_form.html.erb +26 -25
  14. data/app/views/advanced/_advanced_search_help.html.erb +19 -17
  15. data/app/views/advanced/_advanced_search_submit_btns.html.erb +12 -0
  16. data/app/views/advanced/_facet_limit.html.erb +11 -3
  17. data/app/views/advanced/index.html.erb +19 -6
  18. data/app/views/blacklight_advanced_search/_facet_limit.html.erb +8 -3
  19. data/blacklight_advanced_search.gemspec +7 -3
  20. data/config/locales/blacklight_advanced_search.en.yml +12 -0
  21. data/lib/blacklight_advanced_search.rb +28 -17
  22. data/lib/blacklight_advanced_search/advanced_query_parser.rb +26 -15
  23. data/lib/blacklight_advanced_search/redirect_legacy_params_filter.rb +32 -0
  24. data/lib/blacklight_advanced_search/render_constraints_override.rb +33 -7
  25. data/spec/features/blacklight_advanced_search_form_spec.rb +5 -5
  26. data/spec/lib/deep_merge_spec.rb +45 -0
  27. data/spec/test_app_templates/Gemfile.extra +2 -0
  28. data/spec/test_app_templates/lib/generators/test_app_generator.rb +1 -1
  29. metadata +41 -15
  30. data/app/assets/javascripts/blacklight_advanced_search/blacklight_advanced_search_javascript.js +0 -48
  31. data/app/views/advanced/_facet_layout.html.erb +0 -5
@@ -1,3 +1,16 @@
1
- <div class="facets-collapse" id="facets">
2
- <%= render_facet_partials facet_field_names %>
1
+ <%# used to render facets with checkboxes on advanced search form,
2
+ we pretty much just use the built-in blacklight render_facet_partials
3
+ helper.
4
+
5
+ But we've provided a local override of the _facet_limit
6
+ partial in our own `views/advanced/_facet_limit.html.erb`,
7
+ that is written to include checkboxes for form selection.
8
+
9
+ This is the default display of facets, but you can
10
+ also choose to use _advanced_search_facets_as_select,
11
+ for a chosen.js-compatible multi-select.
12
+ %>
13
+
14
+ <div class="advanced-facet-limits panel-group">
15
+ <%= render_facet_partials facet_field_names %>
3
16
  </div>
@@ -0,0 +1,30 @@
1
+ <%# alternate version of facets on form that renders using multi-select.
2
+ Has to copy and paste more code from blacklight than default, making
3
+ it somewhat more fragile.
4
+
5
+ Logic taken from facets_helper_behavior.rb, #render_facet_partials and
6
+ #render_facet_limit.
7
+ %>
8
+
9
+ <% facets_from_request(facet_field_names).each do |display_facet| %>
10
+ <% if should_render_facet?(display_facet) %>
11
+ <div class="form-group advanced-search-facet">
12
+ <%= label_tag display_facet.name.parameterize, :class => "col-sm-3 control-label" do %>
13
+ <%= facet_field_label(display_facet.name) %>
14
+ <% end %>
15
+
16
+ <div class="col-sm-9">
17
+ <%= content_tag(:select, :multiple => true,
18
+ :name => "f_inclusive[#{display_facet.name}][]",
19
+ :id => display_facet.name.parameterize,
20
+ :class => "form-control advanced-search-facet-select") do %>
21
+ <% display_facet.items.each do |facet_item| %>
22
+ <%= content_tag :option, :value => facet_item.value, :selected => facet_value_checked?(display_facet.name, facet_item.value) do %>
23
+ <%= facet_item.label %>&nbsp;&nbsp;(<%= number_with_delimiter facet_item.hits %>)
24
+ <% end %>
25
+ <% end %>
26
+ <% end %>
27
+ </div>
28
+ </div>
29
+ <% end %>
30
+ <% end %>
@@ -1,6 +1,8 @@
1
1
  <%- search_fields_for_advanced_search.each do |key, field_def| -%>
2
- <div class="advanced_search_field">
3
- <%= label_tag key, "#{field_def.label }:" %>
4
- <%= text_field_tag key, label_tag_default_for(key), :class => 'input-block-level' %>
2
+ <div class="form-group advanced-search-field">
3
+ <%= label_tag key, "#{field_def.label }", :class => "col-sm-3 control-label" %>
4
+ <div class="col-sm-9">
5
+ <%= text_field_tag key, label_tag_default_for(key), :class => 'form-control' %>
6
+ </div>
5
7
  </div>
6
8
  <%- end -%>
@@ -1,42 +1,43 @@
1
- <%= form_tag catalog_index_path, :class => 'advanced', :method => :get do %>
1
+ <% unless (search_context_str = render_search_to_s( advanced_search_context)).blank? %>
2
+ <div class="constraints well search_history">
3
+ <h4><%= t 'blacklight_advanced_search.form.search_context' %></h4>
4
+ <%= search_context_str %>
5
+ </div>
6
+ <% end %>
7
+
8
+ <%= form_tag catalog_index_path, :class => 'advanced form-horizontal', :method => :get do %>
2
9
 
3
- <%= search_as_hidden_fields(:params => advanced_search_context ) %>
10
+ <%= render_hash_as_hidden_fields(params_for_search(advanced_search_context, {})) %>
4
11
 
5
- <div class="input_columns row">
12
+ <div class="input-criteria">
6
13
 
7
- <div class="query_column span6">
8
- <h4>Find items that match <%= select_tag(:op, options_for_select({'all'=>'AND','any'=>'OR'}.sort,'all'), :class => 'input-small') %> of the fields below:</h4>
14
+ <div class="query-criteria">
15
+ <h3 class="query-criteria-heading">
16
+ <%= t('blacklight_advanced_search.form.query_criteria_heading_html', :select_menu => select_menu_for_field_operator ) %>
17
+ </h3>
9
18
 
10
19
  <div id="advanced_search">
11
20
  <%= render 'advanced/advanced_search_fields' %>
12
21
  </div>
13
22
  </div>
14
- <div class="limit_column offset1 span4">
15
- <h4><strong>AND</strong> have these attributes:</h4>
23
+
24
+ <div class="limit-criteria">
25
+ <h3 class="limit-criteria-heading"><%= t('blacklight_advanced_search.form.limit_criteria_heading_html')%></h3>
16
26
 
17
27
  <div id="advanced_search_facets" class="limit_input">
18
- <%= render 'advanced_search_facets' %>
28
+ <% if blacklight_config.try(:advanced_search).try {|h| h[:form_facet_partial] } %>
29
+ <%= render blacklight_config.advanced_search[:form_facet_partial] %>
30
+ <% else %>
31
+ <%= render 'advanced_search_facets' %>
32
+ <% end %>
19
33
  </div>
20
34
  </div>
21
35
  </div>
22
36
 
23
- <% unless (search_context_str = render_search_to_s( advanced_search_context)).blank? %>
24
- <div class="constraints">
25
- <h4>Within search:</h4>
26
- <%= search_context_str %>
27
- </div>
28
- <% end %>
37
+ <hr>
29
38
 
30
-
31
- <div class="sort_submit_buttons well clearfix">
32
- <div class="pull-left">
33
- <%= label_tag(:sort, "Sort results by") %>
34
- <%= select_tag(:sort, options_for_select(sort_fields, h(params[:sort]))) %>
35
- <%= hidden_field_tag(:search_field, blacklight_config.advanced_search[:url_key]) %>
36
- </div>
37
- <div class="pull-right">
38
- <%= submit_tag 'Search', :class=>'btn btn-primary', :id=>'advanced_search' %>
39
- <%= link_to "Start over", {:controller => "advanced", :action => "index"}, :class =>"reset btn" %>
40
- </div>
39
+ <div class="sort-submit-buttons clearfix">
40
+ <%= render 'advanced_search_submit_btns' %>
41
41
  </div>
42
+
42
43
  <% end %>
@@ -1,22 +1,24 @@
1
- <div>
2
- <h2>Search tips</h2>
3
- <ul class="advanced_help">
4
- <li>Select "match all" to require all fields.
5
- </li>
1
+ <div class='panel panel-default'>
2
+ <div class="panel-heading">Search tips</div>
3
+ <div class="panel-body">
4
+ <ul class="advanced-help">
5
+ <li>Select "match all" to require all fields.
6
+ </li>
6
7
 
7
- <li>Select "match any" to find at least one field.
8
- </li>
8
+ <li>Select "match any" to find at least one field.
9
+ </li>
9
10
 
10
- <li>Combine keywords and attributes to find specific items.
11
- </li>
11
+ <li>Combine keywords and attributes to find specific items.
12
+ </li>
12
13
 
13
- <li>Use quotation marks to search as a phrase.
14
-
15
- <li>Use "+" before a term to make it required. (Otherwise results matching only some of your terms may be included).</li>
16
-
17
- <li>Use "-" before a word or phrase to exclude.
14
+ <li>Use quotation marks to search as a phrase.
15
+
16
+ <li>Use "+" before a term to make it required. (Otherwise results matching only some of your terms may be included).</li>
17
+
18
+ <li>Use "-" before a word or phrase to exclude.
18
19
 
19
- <li>Use "OR", "AND", and "NOT" to create complex boolean logic. You can use parentheses in your complex expressions. </li>
20
- <li>Truncation and wildcards are not supported - word-stemming is done automatically.</li>
21
- </ul>
20
+ <li>Use "OR", "AND", and "NOT" to create complex boolean logic. You can use parentheses in your complex expressions. </li>
21
+ <li>Truncation and wildcards are not supported - word-stemming is done automatically.</li>
22
+ </ul>
23
+ </div>
22
24
  </div>
@@ -0,0 +1,12 @@
1
+ <div class="sort-buttons pull-left">
2
+ <%= label_tag(:sort, t('blacklight_advanced_search.form.sort_label'), :class => "control-label") %>
3
+
4
+ <%= select_tag(:sort, options_for_select(sort_fields, h(params[:sort])), :class => "form-control sort-select") %>
5
+ <%= hidden_field_tag(:search_field, blacklight_config.advanced_search[:url_key]) %>
6
+ </div>
7
+
8
+ <div class="submit-buttons pull-right">
9
+ <%= link_to t('blacklight_advanced_search.form.start_over'), advanced_search_path, :class =>"btn btn-default" %>
10
+
11
+ <%= submit_tag t('blacklight_advanced_search.form.search_btn'), :class=>'btn btn-primary advanced-search-submit', :id => "advanced-search-submit" %>
12
+ </div>
@@ -1,7 +1,15 @@
1
- <ul>
1
+ <ul class="facet-values list-unstyled blacklight-advanced-facet-select">
2
2
  <% display_facet.items.each do |item| -%>
3
3
  <li>
4
- <%= label_tag "f_inclusive_#{solr_field}[#{item.value.to_sym}]", (check_box_tag "f_inclusive[#{solr_field}][#{item.value.to_sym}]", 1, facet_value_checked?(solr_field, item.value)) + content_tag(:span, item.value, :class => 'facet-value') + " (#{number_with_delimiter item.hits})".html_safe, :class => 'checkbox' %>
4
+ <span class="facet-checkbox">
5
+ <%= check_box_tag "f_inclusive[#{solr_field}][]", item.value.to_sym, facet_value_checked?(solr_field, item.value), :id => "f_inclusive_#{solr_field}_#{item.value.parameterize}"%>
6
+ </span>
7
+
8
+ <span class="label-and-count">
9
+ <%= label_tag "f_inclusive_#{solr_field}_#{item.value.parameterize}" do %>
10
+ <%= render_facet_value(solr_field, item, :suppress_link => true) %>
11
+ <% end %>
12
+ <span>
5
13
  </li>
6
14
  <% end -%>
7
- </ul>
15
+ </ul>
@@ -1,8 +1,21 @@
1
1
  <% @page_title = "More Search Options - #{application_name}" %>
2
- <script type="text/javascript">
3
- $(document).ready(function(){$("form.advanced input:first").focus();});
4
- </script>
5
2
 
6
- <h1 class="advanced">More Search Options</h1>
7
- <%= render 'advanced_search_form' %>
8
- <%= render "advanced_search_help" %>
3
+ <div class="advanced-search-form col-sm-12">
4
+
5
+ <h1 class="advanced page-header">
6
+ <%= t('blacklight_advanced_search.form.title') %>
7
+ <%= link_to t('blacklight_advanced_search.form.start_over'), advanced_search_path, :class =>"btn btn-default pull-right" %>
8
+ </h1>
9
+
10
+ <div class="row">
11
+
12
+ <div class="col-md-8">
13
+ <%= render 'advanced_search_form' %>
14
+ </div>
15
+ <div class="col-md-4">
16
+ <%= render "advanced_search_help" %>
17
+ </div>
18
+
19
+ </div>
20
+
21
+ </div>
@@ -1,9 +1,14 @@
1
1
  <div class="advanced_facet_limit">
2
- <div class="inclusive_or">
2
+ <div class="inclusive_or well">
3
3
  <h4>Any of:</h4>
4
- <ul>
4
+ <ul class="list-unstyled facet-values">
5
5
  <% @advanced_query.filters[solr_field].each do |value| %>
6
- <li><span class="selected"><%= h(value) %></span> <%= link_to(content_tag(:i, '', :class => "icon-remove") + content_tag(:span, '[remove]', :class => 'hide-text'), remove_advanced_facet_param(solr_field, value, params), :class=>"remove") %></li>
6
+ <li>
7
+ <span class="selected"><%= h(value) %></span>
8
+ <%= link_to(remove_advanced_facet_param(solr_field, value, params), :class => "remove") do %>
9
+ <span class="glyphicon glyphicon-remove"></span><span class="sr-only">[remove]</span>
10
+ <% end %>
11
+ </li>
7
12
  <% end %>
8
13
  </ul>
9
14
  </div>
@@ -17,12 +17,16 @@ Gem::Specification.new do |s|
17
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
18
  s.require_paths = ["lib"]
19
19
 
20
-
21
- s.add_dependency "blacklight", "~> 4.0"
20
+ s.add_dependency "blacklight", ">= 5.1", "< 6.0"
22
21
  s.add_dependency "parslet"
23
22
 
23
+ s.add_development_dependency "blacklight_marc"
24
24
  s.add_development_dependency "rails"
25
25
  s.add_development_dependency "rspec-rails"
26
26
  s.add_development_dependency 'jettywrapper', ">= 1.4.2"
27
- s.add_development_dependency 'engine_cart'
27
+
28
+ # engine_cart 0.3 is out, but can't run tests if we're using it,
29
+ # not sure what we need to change to work with 0.3, possibly cbeer
30
+ # might know, in the meantime locking to 0.2.x.
31
+ s.add_development_dependency 'engine_cart', "~> 0.2.2"
28
32
  end
@@ -0,0 +1,12 @@
1
+ en:
2
+ blacklight_advanced_search:
3
+ all: all
4
+ any: any
5
+ form:
6
+ title: More Search Options
7
+ search_context: Within search
8
+ limit_criteria_heading_html: "<strong>AND</strong> have these attributes"
9
+ query_criteria_heading_html: "Find items that match %{select_menu} of"
10
+ sort_label: "Sort results by"
11
+ start_over: "Start over"
12
+ search_btn: 'Search'
@@ -6,10 +6,10 @@ module BlacklightAdvancedSearch
6
6
  autoload :ParsingNestingParser, 'blacklight_advanced_search/parsing_nesting_parser'
7
7
  autoload :FilterParser, 'blacklight_advanced_search/filter_parser'
8
8
  autoload :ParseBasicQ, 'blacklight_advanced_search/parse_basic_q'
9
+ autoload :RedirectLegacyParamsFilter, 'blacklight_advanced_search/redirect_legacy_params_filter'
9
10
 
10
11
  require 'blacklight_advanced_search/version'
11
12
  require 'blacklight_advanced_search/engine'
12
-
13
13
 
14
14
  # Utility method used in our solr search logic.
15
15
  # Merges new_hash into source_hash, but will recursively
@@ -17,22 +17,33 @@ module BlacklightAdvancedSearch
17
17
  # or blank values from new_hash into source_hash, nil or blank values
18
18
  # in new_hash will not overwrite values in source_hash.
19
19
  def self.deep_merge!(source_hash, new_hash)
20
- source_hash.merge!(new_hash) do |key, old, new|
21
- if new.respond_to?(:blank) && new.blank?
22
- old
23
- elsif (old.kind_of?(Hash) and new.kind_of?(Hash))
24
- deep_merge!(old, new)
25
- elsif (old.kind_of?(Array) and new.kind_of?(Array))
26
- old.concat(new).uniq
27
- elsif new.nil?
28
- # Allowing nil values to over-write on merge messes things up.
29
- # don't set a nil value if you really want to force blank, set
30
- # empty string.
31
- old
32
- else
33
- new
34
- end
20
+ # We used to use built-in source_hash.merge() with a block arg
21
+ # to customize merge behavior, but that was breaking in some
22
+ # versions of BL/Rails where source_hash was a kind of HashWithIndifferentAccess,
23
+ # and hwia is unreliable in some versions of Rails. Oh well.
24
+ # https://github.com/projectblacklight/blacklight/issues/827
25
+
26
+ new_hash.each_pair do |key, new_value|
27
+ old = source_hash.fetch(key, nil)
28
+
29
+ source_hash[key] =
30
+ if new_value.respond_to?(:blank) && new.blank?
31
+ old
32
+ elsif (old.kind_of?(Hash) and new_value.kind_of?(Hash))
33
+ deep_merge!(old, new_value)
34
+ old
35
+ elsif (old.kind_of?(Array) and new_value.kind_of?(Array))
36
+ old.concat(new_value).uniq
37
+ elsif new_value.nil?
38
+ # Allowing nil values to over-write on merge messes things up.
39
+ # don't set a nil value if you really want to force blank, set
40
+ # empty string.
41
+ old
42
+ else
43
+ new_value
44
+ end
35
45
  end
36
- end
46
+ source_hash
47
+ end
37
48
 
38
49
  end
@@ -1,34 +1,42 @@
1
1
  module BlacklightAdvancedSearch
2
+ # Can extract query elements from rails #params query params, and then parse
3
+ # them and convert them into a solr query with #to_solr
4
+ #
5
+ # #keyword_queries and #filters, which just return extracted elements of query
6
+ # params, may also be useful in display etc.
2
7
  class QueryParser
3
8
  include ParsingNestingParser # only one strategy currently supported. if BlacklightAdvancedSearch.config[:solr_type] == "parsing_nesting"
4
9
  include FilterParser
5
10
  attr_reader :config, :params
6
-
11
+
7
12
  def initialize(params,config)
8
13
  @params = HashWithIndifferentAccess.new(params)
9
- @config = config
14
+ @config = config
10
15
  end
11
16
 
12
17
  def to_solr
13
18
  @to_solr ||= begin
14
19
  {
15
- :q => process_query(params,config),
16
- :fq => generate_solr_fq()
20
+ :q => process_query(params,config),
21
+ :fq => generate_solr_fq()
17
22
  }
18
23
  end
19
24
  end
20
-
25
+
21
26
  # Returns "AND" or "OR", how #keyword_queries will be combined
22
27
  def keyword_op
23
28
  @params["op"] || "AND"
24
29
  end
25
- # returns advanced-type keyword queries, see also keyword_op
30
+
31
+ # extracts advanced-type keyword query elements from query params,
32
+ # returns as a kash of field => query.
33
+ # see also keyword_op
26
34
  def keyword_queries
27
35
  unless(@keyword_queries)
28
36
  @keyword_queries = {}
29
37
 
30
38
  return @keyword_queries unless @params[:search_field] == ::AdvancedController.blacklight_config.advanced_search[:url_key]
31
-
39
+
32
40
  config.search_fields.each do | key, field_def |
33
41
  if ! @params[ key.to_sym ].blank?
34
42
  @keyword_queries[ key ] = @params[ key.to_sym ]
@@ -37,24 +45,27 @@ module BlacklightAdvancedSearch
37
45
  end
38
46
  return @keyword_queries
39
47
  end
40
- # returns just advanced-type filters
48
+
49
+ # extracts advanced-type filters from query params,
50
+ # returned as a hash of field => [array of values]
41
51
  def filters
42
52
  unless (@filters)
43
53
  @filters = {}
44
54
  return @filters unless @params[:f_inclusive]
45
- @params[:f_inclusive].each_pair do |field, value_hash|
46
- value_hash.each_pair do |value, type|
47
- @filters[field] ||= []
48
- @filters[field] << value
49
- end
50
- end
55
+ @params[:f_inclusive].each_pair do |field, value_array|
56
+ @filters[field] ||= value_array.dup
57
+ end
51
58
  end
52
59
  return @filters
53
60
  end
54
61
 
62
+ def filters_include_value?(field, value)
63
+ filters[field.to_s].try {|array| array.include? value}
64
+ end
65
+
55
66
  def empty?
56
67
  filters.empty? && keyword_queries.empty?
57
68
  end
58
-
69
+
59
70
  end
60
71
  end
@@ -0,0 +1,32 @@
1
+ # Returns a lambda that you can use with a before_filter in your
2
+ # CatalogController to catch and redirect query params using the old
3
+ # style, used prior to blacklight_advanced_search 5.0.
4
+ #
5
+ # This can be used to keep any old bookmarked URLs still working.
6
+ #
7
+ # before_filter BlacklightAdvancedSearch::RedirectLegacyParamsFilter, :only => :index
8
+ #
9
+ module BlacklightAdvancedSearch
10
+ class RedirectLegacyParamsFilter
11
+
12
+ def self.before(controller)
13
+ params = controller.send(:params)
14
+
15
+ if params[:f_inclusive]
16
+ legacy_converted = false
17
+
18
+ params[:f_inclusive].each_pair do |field, value|
19
+ if value.kind_of? Hash
20
+ # old style! convert!
21
+ legacy_converted = true
22
+ params[:f_inclusive][field] = value.keys
23
+ end
24
+ end
25
+
26
+ if legacy_converted
27
+ controller.send(:redirect_to, params, :status => :moved_permanently)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end