blacklight_advanced_search 2.2.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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