diff --git a/app/helpers/blacklight/blacklight_helper_behavior.rb b/app/helpers/blacklight/blacklight_helper_behavior.rb index 2907e7e840..7f9c2fc459 100644 --- a/app/helpers/blacklight/blacklight_helper_behavior.rb +++ b/app/helpers/blacklight/blacklight_helper_behavior.rb @@ -5,25 +5,35 @@ # module Blacklight::BlacklightHelperBehavior include BlacklightUrlHelper + include BlacklightConfigurationHelper include HashAsHiddenFieldsHelper include RenderConstraintsHelper include FacetsHelper + ## + # Get the name of this application, from either: + # - the Rails configuration + # - an i18n string (key: blacklight.application_name; preferred) + # + # @return [String] the application named def application_name return Rails.application.config.application_name if Rails.application.config.respond_to? :application_name t('blacklight.application_name') end + ## # Create links from a documents dynamically # provided export formats. Currently not used by standard BL layouts, # but available for your custom layouts to provide link rel alternates. # # Returns empty string if no links available. # - # :unique => true, will ensure only one link is output for every - # content type, as required eg in atom. Which one 'wins' is arbitrary. - # :exclude => array of format shortnames, formats to not include at all. + # @params [SolrDocument] document + # @params [Hash] options + # @option options [Boolean] :unique ensures only one link is output for every + # content type, e.g. as required by atom + # @option options [Array] :exclude array of format shortnames to not include in the output def render_link_rel_alternates(document=@document, options = {}) options = {:unique => false, :exclude => []}.merge(options) @@ -44,28 +54,43 @@ def render_link_rel_alternates(document=@document, options = {}) return html.html_safe end + ## + # Render OpenSearch headers for this search + # @return [String] def render_opensearch_response_metadata render :partial => 'catalog/opensearch_response_metadata' end + ## + # Render classes for the element + # @return [String] def render_body_class extra_body_classes.join " " end - def render_search_bar - render :partial=>'catalog/search_form' - end - + ## + # List of classes to be applied to the element + # @see render_body_class + # @return [Array] def extra_body_classes @extra_body_classes ||= ['blacklight-' + controller.controller_name, 'blacklight-' + [controller.controller_name, controller.action_name].join('-')] end - def render_document_list_partial options={} - render :partial=>'catalog/document_list' + ## + # Render the search navbar + # @return [String] + def render_search_bar + render :partial=>'catalog/search_form' end - # Save function area for search results 'index' view, normally - # renders next to title. + ## + # Render "docuemnt actions" area for search results view + # (normally renders next to title in the list view) + # + # @param [SolrDocument] document + # @param [Hash] options + # @option options [String] :wrapping_class + # @return [String] def render_index_doc_actions(document, options={}) wrapping_class = options.delete(:wrapping_class) || "index-document-functions" @@ -75,12 +100,18 @@ def render_index_doc_actions(document, options={}) content_tag("div", safe_join(content, "\n"), :class=> wrapping_class) end - # Save function area for item detail 'show' view, normally - # renders next to title. By default includes 'Bookmarks' + ## + # Render "docuemnt actions" for the item detail 'show' view. + # (this normally renders next to title) + # + # By default includes 'Bookmarks' + # + # @param [SolrDocument] document + # @param [Hash] options + # @option options [String] :wrapping_class + # @return [String] def render_show_doc_actions(document=@document, options={}) - # I'm not sure why this key is documentFunctions and #render_index_doc_actions uses wrapping_class. - # TODO: remove documentFunctions key in Blacklight 5.x - wrapping_class = options.delete(:documentFunctions) || options.delete(:wrapping_class) || "documentFunctions" + wrapping_class = options.delete(:wrapping_class) || "documentFunctions" content = [] content << render(:partial => 'catalog/bookmark_control', :locals => {:document=> document}.merge(options)) if render_bookmarks_control? @@ -89,17 +120,34 @@ def render_show_doc_actions(document=@document, options={}) end ## - # Index fields to display for a type of document - def index_fields document=nil - blacklight_config.index_fields + # Determine whether to render a given field in the index view. + # + # @param [SolrDocument] document + # @param [Blacklight::Solr::Configuration::SolrField] solr_field + # @return [Boolean] + def should_render_index_field? document, solr_field + document.has?(solr_field.field) || + (document.has_highlight_field? solr_field.field if solr_field.highlight) || + solr_field.accessor end - def should_render_index_field? document, solr_field + ## + # Determine whether to render a given field in the show view + # + # @param [SolrDocument] document + # @param [Blacklight::Solr::Configuration::SolrField] solr_field + # @return [Boolean] + def should_render_show_field? document, solr_field document.has?(solr_field.field) || (document.has_highlight_field? solr_field.field if solr_field.highlight) || solr_field.accessor end + ## + # Determine whether to display spellcheck suggestions + # + # @param [Blacklight::SolrResponse] response + # @return [Boolean] def should_show_spellcheck_suggestions? response response.total <= spell_check_max and response.spelling.words.size > 0 end @@ -156,69 +204,9 @@ def render_index_field_value *args field_config = index_fields(document)[field] value = options[:value] || get_field_values(document, field, field_config, options) - render_field_value value, field_config end - # Used in the show view for displaying the main solr document heading - def document_heading document=nil - document ||= @document - render_field_value document[blacklight_config.view_config(:show).title_field] || document.id - end - - # Used in the show view for setting the main html document title - def document_show_html_title document=nil - document ||= @document - - if blacklight_config.view_config(:show).html_title_field - render_field_value(document[blacklight_config.view_config(:show).html_title_field]) - else - document_heading document - end - end - - ## - # Render the document "heading" (title) in a content tag - # @overload render_document_heading(tag) - # @overload render_document_heading(document, options) - # @params [SolrDocument] document - # @params [Hash] options - # @options options [Symbol] :tag - def render_document_heading(*args) - options = args.extract_options! - if args.first.is_a? SolrDocument - document = args.shift - tag = options[:tag] - else - document = nil - tag = args.first || options[:tag] - end - - tag ||= :h4 - - content_tag(tag, render_field_value(document_heading(document)), :itemprop => "name") - end - - # Used in the document_list partial (search view) for building a select element - def sort_fields - blacklight_config.sort_fields.map { |key, x| [x.label, x.key] } - end - - # Used in the document list partial (search view) for creating a link to the document show action - def document_show_link_field document=nil - blacklight_config.view_config(document_index_view_type).title_field.to_sym - end - - # Used in the search form partial for building a select tag - def search_fields - search_field_options_for_select - end - - # used in the catalog/_show/_default partial - def document_show_fields document=nil - blacklight_config.show_fields - end - ## # Render the show field label for a document # @@ -265,7 +253,6 @@ def render_document_show_field_label *args # @param [Hash] opts # @options opts [String] :value def render_document_show_field_value *args - options = args.extract_options! document = args.shift || options[:document] @@ -276,6 +263,56 @@ def render_document_show_field_value *args render_field_value value, field_config end + ## + # Get the value of the document's "title" field, or a placeholder + # value (if empty) + # + # @param [SolrDocument] document + # @return [String] + def document_heading document=nil + document ||= @document + render_field_value(document[blacklight_config.view_config(:show).title_field] || document.id) + end + + ## + # Get the document's "title" to display in the element. + # (by default, use the #document_heading) + # + # @see #document_heading + # @param [SolrDocument] document + # @return [String] + def document_show_html_title document=nil + document ||= @document + + if blacklight_config.view_config(:show).html_title_field + render_field_value(document[blacklight_config.view_config(:show).html_title_field]) + else + document_heading document + end + end + + ## + # Render the document "heading" (title) in a content tag + # @overload render_document_heading(tag) + # @overload render_document_heading(document, options) + # @params [SolrDocument] document + # @params [Hash] options + # @options options [Symbol] :tag + def render_document_heading(*args) + options = args.extract_options! + if args.first.is_a? SolrDocument + document = args.shift + tag = options[:tag] + else + document = nil + tag = args.first || options[:tag] + end + + tag ||= :h4 + + content_tag(tag, render_field_value(document_heading(document)), :itemprop => "name") + end + ## # Get the value for a document's field, and prepare to render it. # - highlight_field @@ -287,8 +324,12 @@ def render_document_show_field_value *args # - link_to_search # TODO : maybe this should be merged with render_field_value, and the ugly signature # simplified by pushing some of this logic into the "model" + # @param [SolrDocument] document + # @param [String] field name + # @param [Blacklight::Solr::Configuration::SolrField] solr field configuration + # @param [Hash] options additional options to pass to the rendering helpers def get_field_values document, field, field_config, options = {} - # valuyes + # retrieving values value = case when (field_config and field_config.highlight) # retrieve the document value from the highlighting response @@ -311,7 +352,7 @@ def get_field_values document, field, field_config, options = {} document.get(field, :sep => nil) if field end - # rendering + # rendering values case when (field_config and field_config.helper_method) send(field_config.helper_method, options.merge(:document => document, :field => field, :value => value)) @@ -330,12 +371,12 @@ def get_field_values document, field, field_config, options = {} end end - def should_render_show_field? document, solr_field - document.has?(solr_field.field) || - (document.has_highlight_field? solr_field.field if solr_field.highlight) || - solr_field.accessor - end - + ## + # Render a value (or array of values) from a field + # + # @param [String] value or list of values to display + # @param [Blacklight::Solr::Configuration::SolrField] solr field configuration + # @return [String] def render_field_value value=nil, field_config=nil safe_values = Array(value).collect { |x| x.respond_to?(:force_encoding) ? x.force_encoding("UTF-8") : x } @@ -346,10 +387,19 @@ def render_field_value value=nil, field_config=nil safe_join(safe_values, (field_config.separator if field_config) || field_value_separator) end + ## + # Default separator to use in #render_field_value + # + # @return [String] def field_value_separator ', ' end + ## + # Get the current "view type" (and ensure it is a valid type) + # + # @param [Hash] the query parameters to check + # @return [Symbol] def document_index_view_type query_params=params if query_params[:view] and blacklight_config.view.keys.include? query_params[:view].to_sym query_params[:view].to_sym @@ -358,21 +408,42 @@ def document_index_view_type query_params=params end end - def default_document_index_view_type - blacklight_config.view.keys.first - end - + ## + # Render the document index view + # + # @param [Array<SolrDocument>] list of documents to render + # @param [Hash] locals to pass to the render call + # @return [String] def render_document_index documents = nil, locals = {} documents ||= @document_list render_document_index_with_view(document_index_view_type, documents) end + ## + # Render the document index for a grouped response + def render_grouped_document_index + render :partial => 'catalog/group_default' + end + + ## + # Render the document index for the given view type with the + # list of documents. + # + # This method will interpolate the list of templates with + # the current view, and gracefully handles missing templates. + # + # @see #document_index_path_templates + # + # @param [String] view type + # @param [Array<SolrDocument>] list of documents to render + # @param [Hash] locals to pass to the render call + # @return [String] def render_document_index_with_view view, documents, locals = {} document_index_path_templates.each do |str| # XXX rather than handling this logic through exceptions, maybe there's a Rails internals method # for determining if a partial template exists.. begin - return render(:partial => (str % { :index_view_type => view }), :locals => { :documents => documents }) + return render(:partial => (str % { :index_view_type => view }), :locals => locals.merge(:documents => documents) ) rescue ActionView::MissingTemplate nil end @@ -381,7 +452,11 @@ def render_document_index_with_view view, documents, locals = {} return "" end - # a list of document partial templates to try to render for #render_document_index + ## + # A list of document partial templates to attempt to render + # + # @see #render_document_index_with_view + # @return [Array<String>] def document_index_path_templates # first, the legacy template names for backwards compatbility # followed by the new, inheritable style @@ -389,37 +464,53 @@ def document_index_path_templates @document_index_path_templates ||= ["document_%{index_view_type}", "catalog/document_%{index_view_type}", "catalog/document_list"] end - # Return a normalized partial name that can be used to contruct view partial path + ## + # Return a normalized partial name for rendering a single document + # + # @param [SolrDocument] + # @return [String] def document_partial_name(document) - # .to_s is necessary otherwise the default return value is not always a string - # using "_" as sep. to more closely follow the views file naming conventions - # parameterize uses "-" as the default sep. which throws errors display_type = document[blacklight_config.view_config(:show).display_type_field] return 'default' unless display_type display_type = display_type.join(" ") if display_type.respond_to?(:join) + # .to_s is necessary otherwise the default return value is not always a string + # using "_" as sep. to more closely follow the views file naming conventions + # parameterize uses "-" as the default sep. which throws errors "#{display_type.gsub("-"," ")}".parameterize("_").to_s end - def render_document_partials(doc, actions = [], locals ={}) - safe_join(actions.map do |action_name| + ## + # Return the list of partials for a given solr document + # @param [SolrDocument] + # @return [String] + def render_document_partials(doc, partials = [], locals ={}) + safe_join(partials.map do |action_name| render_document_partial(doc, action_name, locals) end, "\n") end - # given a doc and action_name, this method attempts to render a partial template - # based on the value of doc[:format] - # if this value is blank (nil/empty) the "default" is used - # if the partial is not found, the "default" partial is rendered instead - def render_document_partial(doc, action_name, locals = {}) + ## + # Given a doc and a base name for a partial, this method will attempt to render + # an appropriate partial based on the document format and view type. + # + # If a partial that matches the document format is not found, + # render a default partial for the base name. + # + # @see #document_partial_path_templates + # + # @param [SolrDocument] doc + # @param [String] base name for the partial + # @param [Hash] locales to pass through to the partials + def render_document_partial(doc, base_name, locals = {}) format = document_partial_name(doc) document_partial_path_templates.each do |str| # XXX rather than handling this logic through exceptions, maybe there's a Rails internals method # for determining if a partial template exists.. begin - return render :partial => (str % { :action_name => action_name, :format => format, :index_view_type => document_index_view_type }), :locals=>locals.merge({:document=>doc}) + return render :partial => (str % { :action_name => base_name, :format => format, :index_view_type => document_index_view_type }), :locals=>locals.merge(:document=>doc) rescue ActionView::MissingTemplate nil end @@ -428,7 +519,15 @@ def render_document_partial(doc, action_name, locals = {}) return '' end - # a list of document partial templates to try to render for #render_document_partial + ## + # A list of document partial templates to try to render for a document + # + # The partial names will be interpolated with the following variables: + # - action_name: (e.g. index, show) + # - index_view_type: (the current view type, e.g. list, gallery) + # - format: the document's format (e.g. book) + # + # @see #render_document_partial def document_partial_path_templates # first, the legacy template names for backwards compatbility # followed by the new, inheritable style @@ -436,9 +535,15 @@ def document_partial_path_templates @partial_path_templates ||= ["%{action_name}_%{index_view_type}_%{format}", "%{action_name}_%{index_view_type}_default", "%{action_name}_%{format}", "%{action_name}_default", "catalog/%{action_name}_%{format}", "catalog/_%{action_name}_partials/%{format}", "catalog/_%{action_name}_partials/default"] end - - - def render_document_index_label doc, opts + ## + # Render the document index heading + # + # @param [SolrDocument] doc + # @param [Hash] opts + # @option opts [Symbol] :label Render the given field from the document + # @option opts [Proc] :label Evaluate the given proc + # @option opts [String] :label Render the given string + def render_document_index_label doc, opts = {} label = nil label ||= doc.get(opts[:label], :sep => nil) if opts[:label].instance_of? Symbol label ||= opts[:label].call(doc, opts) if opts[:label].instance_of? Proc @@ -447,10 +552,15 @@ def render_document_index_label doc, opts render_field_value label end - # Use case, you want to render an html partial from an XML (say, atom) - # template. Rails API kind of lets us down, we need to hack Rails internals - # a bit. code taken from: + ## + # Render a partial of an arbitrary format inside a + # template of a different format. (e.g. render an HTML + # partial from an XML template) + # code taken from: # http://stackoverflow.com/questions/339130/how-do-i-render-a-partial-of-a-different-format-in-rails (zgchurch) + # + # @param [String] format suffix + # @yield def with_format(format, &block) old_formats = formats self.formats = [format] @@ -467,17 +577,9 @@ def render_grouped_response? response = @response end ## - # Render the grouped response - def render_grouped_document_index grouped_key = nil - render :partial => 'catalog/group_default' - end - + # Determine whether to render the bookmarks control def render_bookmarks_control? has_user_authentication_provider? and current_or_guest_user.present? end - def spell_check_max - blacklight_config.spell_max - end - end diff --git a/app/helpers/blacklight/catalog_helper_behavior.rb b/app/helpers/blacklight/catalog_helper_behavior.rb index 054b7d311a..ffdb06aab2 100644 --- a/app/helpers/blacklight/catalog_helper_behavior.rb +++ b/app/helpers/blacklight/catalog_helper_behavior.rb @@ -1,10 +1,13 @@ # -*- encoding : utf-8 -*- module Blacklight::CatalogHelperBehavior + ## # Override the Kaminari page_entries_info helper with our own, blacklight-aware - # implementation + # implementation. + # Displays the "showing X through Y of N" message. # - # Pass in an RSolr::Response. Displays the "showing X through Y of N" message. + # @param [RSolr::Resource] (or other Kaminari-compatible objects) + # @return [String] def page_entries_info(collection, options = {}) entry_name = if options[:entry_name] options[:entry_name] @@ -40,68 +43,122 @@ def page_entries_info(collection, options = {}) end end + ## + # Get the offset counter for a document + # + # @param [Integer] document index + # @return [Integer] def document_counter_with_offset idx unless render_grouped_response? idx + 1 + @response.params[:start].to_i end end - # Like #page_entries_info above, but for an individual - # item show page. Displays "showing X of Y items" message. Actually takes - # data from session though (not a great design). - # Code should call this method rather than interrogating session directly, - # because implementation of where this data is stored/retrieved may change. + ## + # Like #page_entries_info above, but for an individual + # item show page. Displays "showing X of Y items" message. + # + # @see #page_entries_info + # @return [String] def item_page_entry_info t('blacklight.search.entry_pagination_info.other', :current => number_with_delimiter(search_session[:counter]), :total => number_with_delimiter(search_session[:total]), :count => search_session[:total].to_i).html_safe end + ## # Look up search field user-displayable label # based on params[:qt] and blacklight_configuration. def search_field_label(params) h( label_for_search_field(params[:search_field]) ) end + ## + # Look up the current sort field, or provide the default if none is set + # + # @return [Blacklight::Configuration::SortField] def current_sort_field - blacklight_config.sort_fields[params[:sort]] || (blacklight_config.sort_fields.first ? blacklight_config.sort_fields.first.last : nil ) + blacklight_config.sort_fields[params[:sort]] || default_sort_field end + ## + # Look up the current per page value, or the default if none if set + # + # @return [Integer] def current_per_page - (@response.rows if @response and @response.rows > 0) || params.fetch(:per_page, (blacklight_config.per_page.first unless blacklight_config.per_page.blank?)).to_i + (@response.rows if @response and @response.rows > 0) || params.fetch(:per_page, default_per_page).to_i end - # Export to Refworks URL, called in _show_tools + ## + # Export to Refworks URL + # + # @param [SolrDocument] + # @return [String] def refworks_export_url(document = @document) "http://www.refworks.com/express/expressimport.asp?vendor=#{CGI.escape(application_name)}&filter=MARC%20Format&encoding=65001&url=#{CGI.escape(polymorphic_path(document, :format => 'refworks_marc_txt', :only_path => false))}" end + ## + # Get the classes to add to a document's div + # + # @return [String] def render_document_class(document = @document) 'blacklight-' + document.get(blacklight_config.view_config(document_index_view_type_field).display_type_field).parameterize rescue nil end + ## + # Render the sidebar partial for a document + # + # @param [SolrDocument] + # @return [String] def render_document_sidebar_partial(document = @document) render :partial => 'show_sidebar' end + ## + # Check if any search parameters have been set + # @return [Boolean] def has_search_parameters? !params[:q].blank? or !params[:f].blank? or !params[:search_field].blank? end + ## + # Should we display the sort and per page widget? + # + # @param [Blacklight::SolrResponse] + # @return [Boolean] def show_sort_and_per_page? response = nil response ||= @response !response.empty? end + ## + # If no search parameters have been given, we should + # auto-focus the user's cursor into the searchbox + # + # @return [Boolean] def should_autofocus_on_search_box? controller.is_a? Blacklight::Catalog and action_name == "index" and !has_search_parameters? end + ## + # Does the document have a thumbnail to render? + # + # @param [SolrDocument] + # @return [Boolean] def has_thumbnail? document blacklight_config.view_config(document_index_view_type).thumbnail_method or blacklight_config.view_config(document_index_view_type).thumbnail_field && document.has?(blacklight_config.view_config(document_index_view_type).thumbnail_field) end + ## + # Render the thumbnail, if available, for a document and + # link it to the document record. + # + # @param [SolrDocument] + # @param [Hash] options to pass to the image tag + # @param [Hash] url options to pass to #link_to_document + # @return [String] def render_thumbnail_tag document, image_options = {}, url_options = {} value = if blacklight_config.view_config(document_index_view_type).thumbnail_method send(blacklight_config.view_config(document_index_view_type).thumbnail_method, document, image_options) @@ -114,24 +171,40 @@ def render_thumbnail_tag document, image_options = {}, url_options = {} end end + ## + # Get the URL to a document's thumbnail image + # + # @param [SolrDocument] + # @return [String] def thumbnail_url document if document.has? blacklight_config.view_config(document_index_view_type).thumbnail_field document.first(blacklight_config.view_config(document_index_view_type).thumbnail_field) end end + ## + # Get url parameters to a search within a grouped result set + # + # @param [Blacklight::SolrResponse::Group] + # @return [Hash] def add_group_facet_params_and_redirect group add_facet_params_and_redirect(group.field, group.key) end - def has_alternative_views? - blacklight_config.view.keys.length > 1 - end - + ## + # Render the view type icon for the results view picker + # + # @param [String] + # @return [String] def render_view_type_group_icon view content_tag :span, '', class: "glyphicon #{blacklight_config.view[view].icon_class || default_view_type_group_icon_classes(view) }" end + ## + # Get the default view type classes for a view in the results view picker + # + # @param [String] + # @return [String] def default_view_type_group_icon_classes view "glyphicon-#{view.to_s.parameterize } view-icon-#{view.to_s.parameterize}" end diff --git a/app/helpers/blacklight/configuration_helper_behavior.rb b/app/helpers/blacklight/configuration_helper_behavior.rb new file mode 100644 index 0000000000..ea8f0c6916 --- /dev/null +++ b/app/helpers/blacklight/configuration_helper_behavior.rb @@ -0,0 +1,61 @@ +module Blacklight::ConfigurationHelperBehavior + + ## + # Index fields to display for a type of document + # + # @param [SolrDocument] document + # @return [Array<Blacklight::Solr::Configuration::SolrField>] + def index_fields document=nil + blacklight_config.index_fields + end + + # Used in the document_list partial (search view) for building a select element + def sort_fields + blacklight_config.sort_fields.map { |key, x| [x.label, x.key] } + end + + # Used in the search form partial for building a select tag + def search_fields + search_field_options_for_select + end + + # used in the catalog/_show/_default partial + def document_show_fields document=nil + blacklight_config.show_fields + end + + ## + # Get the default index view type + def default_document_index_view_type + blacklight_config.view.keys.first + end + + ## + # Check if there are alternative views configuration + def has_alternative_views? + blacklight_config.view.keys.length > 1 + end + + ## + # Maximum number of results for spell checking + def spell_check_max + blacklight_config.spell_max + end + + # Used in the document list partial (search view) for creating a link to the document show action + def document_show_link_field document=nil + blacklight_config.view_config(document_index_view_type).title_field.to_sym + end + + ## + # Default sort field + def default_sort_field + blacklight_config.sort_fields.first.last if blacklight_config.sort_fields.first + end + + ## + # The default value for search results per page + def default_per_page + blacklight_config.per_page.first unless blacklight_config.per_page.blank? + end +end \ No newline at end of file diff --git a/app/helpers/blacklight/facets_helper_behavior.rb b/app/helpers/blacklight/facets_helper_behavior.rb index 8601e9273d..fd7f88cabe 100644 --- a/app/helpers/blacklight/facets_helper_behavior.rb +++ b/app/helpers/blacklight/facets_helper_behavior.rb @@ -2,11 +2,23 @@ module Blacklight::FacetsHelperBehavior include Blacklight::Facet + ## + # Check if any of the given fields have values + # + # @param [Array<String>] + # @param [Hash] options + # @return [Boolean] def has_facet_values? fields = facet_field_names, options = {} facets_from_request(fields).any? { |display_facet| !display_facet.items.empty? } end - # Render a collection of facet fields + ## + # Render a collection of facet fields. + # @see #render_facet_limit + # + # @param [Array<String>] + # @param [Hash] options + # @return String def render_facet_partials fields = facet_field_names, options = {} safe_join(facets_from_request(fields).map do |display_facet| render_facet_limit(display_facet, options) @@ -14,14 +26,17 @@ def render_facet_partials fields = facet_field_names, options = {} end - # used in the catalog/_facets partial and elsewhere + ## # Renders a single section for facet limit with a specified # solr field used for faceting. Can be over-ridden for custom # display on a per-facet basis. # # @param [Blacklight::SolrResponse::Facets::FacetField] display_facet # @param [Hash] options parameters to use for rendering the facet limit partial - # + # @option options [String] :partial partial to render + # @option options [String] :layout partial layout to render + # @option options [Hash] :locals locals to pass to the partial + # @return [String] def render_facet_limit(display_facet, options = {}) return if not should_render_facet?(display_facet) options = options.dup @@ -39,7 +54,9 @@ def render_facet_limit(display_facet, options = {}) # Determine if Blacklight should render the display_facet or not # # By default, only render facets with items. - # @param [Blacklight::SolrResponse::Facets::FacetField] display_facet + # + # @param [Blacklight::SolrResponse::Facets::FacetField] display_facet + # @return [Boolean] def should_render_facet? display_facet # display when show is nil or true facet_config = facet_configuration_for_field(display_facet.name) @@ -63,15 +80,21 @@ def should_render_facet? display_facet end ## - # if the facet is 'active', don't collapse - # if the facet is configured to collapse (the default), collapse - # if the facet is configured not to collapse, don't collapse + # Determine whether a facet should be rendered as collapsed or not. + # - if the facet is 'active', don't collapse + # - if the facet is configured to collapse (the default), collapse + # - if the facet is configured not to collapse, don't collapse + # + # @param [Blacklight::Configuration::FacetField] + # @return [Boolean] def should_collapse_facet? facet_field !facet_field_in_params?(facet_field.field) && facet_field.collapse end - # the name of the partial to use to render a facet field. Can be over-ridden for custom - # display on a per-facet basis. + ## + # the name of the partial to use to render a facet field. + # + # @return [String] def facet_partial_name(display_facet = nil) config = facet_configuration_for_field(display_facet.name) name = config.try(:partial) @@ -79,17 +102,17 @@ def facet_partial_name(display_facet = nil) name ||= "facet_limit" end - # - # facet param helpers -> - # - + ## # Standard display of a facet value in a list. Used in both _facets sidebar # partial and catalog/facet expanded list. Will output facet value name as - # a link to add that to your restrictions, with count in parens. - # first arg item is a facet value item from rsolr-ext. - # options consist of: - # :suppress_link => true # do not make it a link - # :route_set => my_engine # call link_to on engine routes. + # a link to add that to your restrictions, with count in parens. + # + # @param [Blacklight::SolrResponse::Facets::FacetField] + # @param [String] facet item + # @param [Hash] options + # @option options [Boolean] :suppress_link display the facet, but don't link to it + # @option options [Rails::Engine] :route_set route set to use to render the link + # @return [String] def render_facet_value(facet_solr_field, item, options ={}) scope = options.delete(:route_set) || self path = scope.url_for(add_facet_params_and_redirect(facet_solr_field, item).merge(only_path: true)) @@ -98,8 +121,9 @@ def render_facet_value(facet_solr_field, item, options ={}) end + render_facet_count(item.hits) end - # Standard display of a SELECTED facet value, no link, special span - # with class, and 'remove' button. + ## + # Standard display of a SELECTED facet value (e.g. without a link and with a remove button) + # @params (see #render_facet_value) def render_selected_facet_value(facet_solr_field, item) content_tag(:span, :class => "facet-label") do content_tag(:span, facet_display_value(facet_solr_field, item), :class => "selected") + @@ -108,20 +132,35 @@ def render_selected_facet_value(facet_solr_field, item) end + render_facet_count(item.hits, :classes => ["selected"]) end + ## # Renders a count value for facet limits. Can be over-ridden locally # to change style. And can be called by plugins to get consistent display. # - # option :class takes an array of classes to add to count span. + # @param [Integer] number of facet results + # @param [Hash] options + # @option options [Array<String>] an array of classes to add to count span. + # @return [String] def render_facet_count(num, options = {}) classes = (options[:classes] || []) << "facet-count" content_tag("span", t('blacklight.search.facets.count', :number => num), :class => classes) end + ## + # Are any facet restrictions for a field in the query parameters? + # + # @param [String] facet field + # @return [Boolean] def facet_field_in_params? field params[:f] and params[:f][field] end - # true or false, depending on whether the field and value is in params[:f] + ## + # Check if the query parameters have the given facet field with the + # given value. + # + # @param [Object] facet field + # @param [Object] facet value + # @return [Boolean] def facet_in_params?(field, item) if item and item.respond_to? :field field = item.field @@ -132,6 +171,12 @@ def facet_in_params?(field, item) facet_field_in_params?(field) and params[:f][field].include?(value) end + ## + # Get the displayable version of a facet's value + # + # @param [Object] field + # @param [String] item value + # @return [String] def facet_display_value field, item facet_config = facet_configuration_for_field(field) diff --git a/app/helpers/blacklight/hash_as_hidden_fields_helper_behavior.rb b/app/helpers/blacklight/hash_as_hidden_fields_helper_behavior.rb index 9c73fb2f41..83419efda3 100644 --- a/app/helpers/blacklight/hash_as_hidden_fields_helper_behavior.rb +++ b/app/helpers/blacklight/hash_as_hidden_fields_helper_behavior.rb @@ -11,9 +11,12 @@ # to form fields used for sort and change per-page module Blacklight::HashAsHiddenFieldsHelperBehavior + ## # Writes out zero or more <input type="hidden"> elements, completely # representing a hash passed in using Rails-style request parameters - # for hashes nested with arrays and other hashes. + # for hashes nested with arrays and other hashes. + # @param [Hash] + # @return [String] def render_hash_as_hidden_fields(hash) hidden_fields = [] diff --git a/app/helpers/blacklight/render_constraints_helper_behavior.rb b/app/helpers/blacklight/render_constraints_helper_behavior.rb index 326836ebdc..470174efc7 100644 --- a/app/helpers/blacklight/render_constraints_helper_behavior.rb +++ b/app/helpers/blacklight/render_constraints_helper_behavior.rb @@ -7,16 +7,30 @@ # search results page (render_constraints(_*)) module Blacklight::RenderConstraintsHelperBehavior + ## + # Check if the query has any constraints defined (a query, facet, etc) + # + # @param [Hash] query parameters + # @return [Boolean] def query_has_constraints?(localized_params = params) !(localized_params[:q].blank? and localized_params[:f].blank?) end - # Render actual constraints, not including header or footer + ## + # Render the actual constraints, not including header or footer # info. + # + # @param [Hash] query parameters + # @return [String] def render_constraints(localized_params = params) render_constraints_query(localized_params) + render_constraints_filters(localized_params) end + ## + # Render the query constraints + # + # @param [Hash] query parameters + # @return [String] def render_constraints_query(localized_params = params) # So simple don't need a view template, we can just do it here. if (!localized_params[:q].blank?) @@ -36,6 +50,11 @@ def render_constraints_query(localized_params = params) end end + ## + # Render the facet constraints + # + # @param [Hash] query parameters + # @return [String] def render_constraints_filters(localized_params = params) return "".html_safe unless localized_params[:f] content = [] @@ -46,6 +65,13 @@ def render_constraints_filters(localized_params = params) safe_join(content.flatten, "\n") end + ## + # Render a single facet's constraint + # + # @param [String] facet field + # @param [Array<String>] selected facet values + # @param [Hash] query parameters + # @return [String] def render_filter_element(facet, values, localized_params) facet_config = facet_configuration_for_field(facet) @@ -66,15 +92,12 @@ def render_filter_element(facet, values, localized_params) # # Can pass in nil label if desired. # - # options: - # [:remove] - # url to execute for a 'remove' action - # [:classes] - # can be an array of classes to add to container span for constraint. - # [:escape_label] - # default true, HTML escape. - # [:escape_value] - # default true, HTML escape. + # @param [String] label to display + # @param [String] value to display + # @param [Hash] options + # @option options [String] :remove url to execute for a 'remove' action + # @option options [Array<String>] :classes an array of classes to add to container span for constraint. + # @return [String] def render_constraint_element(label, value, options = {}) render(:partial => "catalog/constraints_element", :locals => {:label => label, :value => value, :options => options}) end diff --git a/app/helpers/blacklight/search_history_constraints_helper_behavior.rb b/app/helpers/blacklight/search_history_constraints_helper_behavior.rb index 2d8fa9c989..ab7a9a502e 100644 --- a/app/helpers/blacklight/search_history_constraints_helper_behavior.rb +++ b/app/helpers/blacklight/search_history_constraints_helper_behavior.rb @@ -16,6 +16,8 @@ def render_search_to_s(params) render_search_to_s_filters(params) end + ## + # Render the search query constraint def render_search_to_s_q(params) return "".html_safe if params[:q].blank? @@ -26,6 +28,8 @@ def render_search_to_s_q(params) render_search_to_s_element(label , render_filter_value(params[:q]) ) end + ## + # Render the search facet constraints def render_search_to_s_filters(params) return "".html_safe unless params[:f] @@ -45,11 +49,15 @@ def render_search_to_s_element(key, value, options = {}) content_tag(:span, render_filter_name(key) + content_tag(:span, value, :class => 'filterValues'), :class => 'constraint') end + ## + # Render the name of the facet def render_filter_name name return "".html_safe if name.blank? content_tag(:span, t('blacklight.search.filters.label', :label => name), :class => 'filterName') end + ## + # Render the value of the facet def render_filter_value value, key = nil display_value = value display_value = facet_display_value(key, value) if key diff --git a/app/helpers/blacklight/url_helper_behavior.rb b/app/helpers/blacklight/url_helper_behavior.rb index 7d6b5eb6f2..49ff5f71f0 100644 --- a/app/helpers/blacklight/url_helper_behavior.rb +++ b/app/helpers/blacklight/url_helper_behavior.rb @@ -10,18 +10,24 @@ def link_to_document(doc, opts={:label=>nil, :counter => nil}) link_to label, doc, search_session_params(opts[:counter]).merge(opts.reject { |k,v| [:label, :counter].include? k }) end + ## + # Link to the previous document in the current search context def link_to_previous_document(previous_document) link_to_unless previous_document.nil?, raw(t('views.pagination.previous')), previous_document, search_session_params(search_session[:counter].to_i - 1).merge(:class => "previous", :rel => 'prev') do content_tag :span, raw(t('views.pagination.previous')), :class => 'previous' end end + ## + # Link to the next document in the current search context def link_to_next_document(next_document) link_to_unless next_document.nil?, raw(t('views.pagination.next')), next_document, search_session_params(search_session[:counter].to_i + 1).merge(:class => "next", :rel => 'next') do content_tag :span, raw(t('views.pagination.next')), :class => 'next' end end + ## + # Current search context parameters def search_session_params counter { :'data-counter' => counter, :'data-search_id' => current_search_session.try(:id) } end @@ -175,6 +181,7 @@ def add_facet_params_and_redirect(field, item) new_params[:action] = "index" new_params end + # copies the current params (or whatever is passed in as the 3rd arg) # removes the field value from params[:f] # removes the field if there are no more values in params[:f][field] diff --git a/app/helpers/blacklight_configuration_helper.rb b/app/helpers/blacklight_configuration_helper.rb new file mode 100644 index 0000000000..e75427a54d --- /dev/null +++ b/app/helpers/blacklight_configuration_helper.rb @@ -0,0 +1,3 @@ +module BlacklightConfigurationHelper + include Blacklight::ConfigurationHelperBehavior +end \ No newline at end of file diff --git a/app/views/catalog/_constraints_element.html.erb b/app/views/catalog/_constraints_element.html.erb index 8a33b1700c..5ac5dae624 100644 --- a/app/views/catalog/_constraints_element.html.erb +++ b/app/views/catalog/_constraints_element.html.erb @@ -5,26 +5,24 @@ # :remove => url for a remove constraint link # :classes => array of classes to add to container span options ||= {} - options[:escape_label] = true unless options.has_key?(:escape_label) - options[:escape_value] = true unless options.has_key?(:escape_value) -%> <span class="btn-group appliedFilter constraint <%= options[:classes].join(" ") if options[:classes] %>"> <a href="#" class="constraint-value btn btn-sm btn-default btn-disabled"> <%- unless label.blank? -%> - <span class="filterName"><%= options[:escape_label] ? h(label) : raw(label) %></span> + <span class="filterName"><%= label %></span> <%- end -%> <%- unless value.blank? -%> - <span class="filterValue"><%= options[:escape_value] ? h(value) : raw(value) %></span> + <span class="filterValue"><%= value %></span> <%- end -%> </a> <%- unless options[:remove].blank? -%> <% accessible_remove_label = content_tag :span, :class => 'sr-only' do if label.blank? - t('blacklight.search.filters.remove.value', :value => (options[:escape_value] ? h(value) : value)) + t('blacklight.search.filters.remove.value', :value => value) else - t('blacklight.search.filters.remove.label_value', :label => (options[:escape_label] ? h(label) : label), :value => (options[:escape_value] ? h(value) : value)) + t('blacklight.search.filters.remove.label_value', :label => label, :value => value) end end %> diff --git a/lib/blacklight/solr/document.rb b/lib/blacklight/solr/document.rb index b4b96aa6e5..545d03e104 100644 --- a/lib/blacklight/solr/document.rb +++ b/lib/blacklight/solr/document.rb @@ -114,7 +114,7 @@ def id end def to_param - id + id.to_s end def as_json(options = nil) diff --git a/lib/blacklight/utils.rb b/lib/blacklight/utils.rb index 967913288d..3cb8ae1314 100644 --- a/lib/blacklight/utils.rb +++ b/lib/blacklight/utils.rb @@ -1,7 +1,7 @@ require 'ostruct' module Blacklight class OpenStructWithHashAccess < OpenStruct - delegate :keys, :has_key?, :delete, :length, :reject!, :select!, :include, :fetch, :to => :to_h + delegate :keys, :each, :map, :has_key?, :delete, :length, :reject!, :select!, :include, :fetch, :to => :to_h def []=(key, value) send "#{key}=", value diff --git a/lib/generators/blacklight/install_generator.rb b/lib/generators/blacklight/install_generator.rb index cec3cc3fb0..a53cb2e19e 100644 --- a/lib/generators/blacklight/install_generator.rb +++ b/lib/generators/blacklight/install_generator.rb @@ -88,5 +88,9 @@ def add_sass_configuration EOF end end + + def inject_blacklight_i18n_strings + copy_file "blacklight.en.yml", "config/locales/blacklight.en.yml" + end end end \ No newline at end of file diff --git a/lib/generators/blacklight/templates/blacklight.en.yml b/lib/generators/blacklight/templates/blacklight.en.yml new file mode 100644 index 0000000000..ff834b2f07 --- /dev/null +++ b/lib/generators/blacklight/templates/blacklight.en.yml @@ -0,0 +1,3 @@ +en: + blacklight: + application_name: 'Blacklight' \ No newline at end of file diff --git a/spec/helpers/configuration_helper_spec.rb b/spec/helpers/configuration_helper_spec.rb new file mode 100644 index 0000000000..86f4f33f0f --- /dev/null +++ b/spec/helpers/configuration_helper_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +describe BlacklightConfigurationHelper do + let(:blacklight_config) { Blacklight::Configuration.new } + let(:config_value) { double() } + + before :each do + helper.stub(blacklight_config: blacklight_config) + end + + describe "#index_fields" do + it "should pass through the configuration" do + blacklight_config.stub(index_fields: config_value) + expect(helper.index_fields).to eq config_value + end + end + + describe "#sort_fields" do + it "should convert the sort fields to select-ready values" do + blacklight_config.stub(sort_fields: { 'a' => double(key: 'a', label: 'a'), 'b' => double(key: 'b', label: 'b'), }) + expect(helper.sort_fields).to eq [['a', 'a'], ['b', 'b']] + end + end + + describe "#document_show_fields" do + it "should pass through the configuration" do + blacklight_config.stub(show_fields: config_value) + expect(helper.document_show_fields).to eq config_value + end + end + + describe "#default_document_index_view_type" do + it "should be the first configured index view" do + blacklight_config.stub(view: { 'a' => true, 'b' => true}) + expect(helper.default_document_index_view_type).to eq 'a' + end + end + + describe "#has_alternative_views?" do + subject { helper.has_alternative_views?} + describe "with a single view defined" do + it { should be_false } + end + + describe "with multiple views defined" do + before do + blacklight_config.view.abc + blacklight_config.view.xyz + end + + it { should be_true } + end + end + + describe "#spell_check_max" do + it "should pass through the configuration" do + blacklight_config.stub(spell_max: config_value) + expect(helper.spell_check_max).to eq config_value + end + end +end \ No newline at end of file diff --git a/spec/helpers/url_helper_spec.rb b/spec/helpers/url_helper_spec.rb index 7c968bb6d0..10b5640910 100644 --- a/spec/helpers/url_helper_spec.rb +++ b/spec/helpers/url_helper_spec.rb @@ -225,6 +225,13 @@ def params @document = SolrDocument.new('id'=>'123456') expect(link_to_document(@document,:label=>"Some crazy long label...")).to_not match(/title=/) end + + it "should work with integer ids" do + data = {'id'=> 123456 } + @document = SolrDocument.new(data) + expect(link_to_document(@document)).to have_selector("a") + end + end describe "link_to_previous_search" do diff --git a/spec/lib/blacklight/solr/document_spec.rb b/spec/lib/blacklight/solr/document_spec.rb index da0dfee1dd..03de296aa2 100644 --- a/spec/lib/blacklight/solr/document_spec.rb +++ b/spec/lib/blacklight/solr/document_spec.rb @@ -44,6 +44,14 @@ def my_extension_method @document = MockDocument.new :id => 'asdf', :my_unique_key => '1234' expect(@document.id).to eq '1234' end + + end + + describe "#to_param" do + it "should be a string" do + @document = MockDocument.new :id => 1234 + expect(@document.to_param).to eq '1234' + end end context "Extendability" do diff --git a/spec/views/catalog/_constraints_element.html.erb_spec.rb b/spec/views/catalog/_constraints_element.html.erb_spec.rb index 099e93a80f..623795f4aa 100644 --- a/spec/views/catalog/_constraints_element.html.erb_spec.rb +++ b/spec/views/catalog/_constraints_element.html.erb_spec.rb @@ -52,7 +52,7 @@ describe "with no escaping" do before do - render( :partial => "catalog/constraints_element", :locals => {:label => "<span class='custom_label'>my label</span>", :value => "<span class='custom_value'>my value</span>", :options => {:escape_label => false, :escape_value => false}} ) + render( :partial => "catalog/constraints_element", :locals => {:label => "<span class='custom_label'>my label</span>".html_safe, :value => "<span class='custom_value'>my value</span>".html_safe} ) end it "should not escape key and value" do expect(rendered).to have_selector("span.appliedFilter.constraint span.filterName span.custom_label")