diff --git a/app/components/blacklight/constraint_component.rb b/app/components/blacklight/constraint_component.rb index de36d61116..3bde99b50f 100644 --- a/app/components/blacklight/constraint_component.rb +++ b/app/components/blacklight/constraint_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class ConstraintComponent < ::ViewComponent::Base + class ConstraintComponent < Blacklight::Component with_collection_parameter :facet_item_presenter def initialize(facet_item_presenter:, classes: 'filter', layout: Blacklight::ConstraintLayoutComponent) diff --git a/app/components/blacklight/constraint_layout_component.rb b/app/components/blacklight/constraint_layout_component.rb index eb5b797ae5..f1e1387a56 100644 --- a/app/components/blacklight/constraint_layout_component.rb +++ b/app/components/blacklight/constraint_layout_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class ConstraintLayoutComponent < ::ViewComponent::Base + class ConstraintLayoutComponent < Blacklight::Component def initialize(value:, label: nil, remove_path: nil, classes: nil, search_state: nil) @value = value @label = label diff --git a/app/components/blacklight/constraints_component.rb b/app/components/blacklight/constraints_component.rb index b00747ed43..4b9c836430 100644 --- a/app/components/blacklight/constraints_component.rb +++ b/app/components/blacklight/constraints_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class ConstraintsComponent < ::ViewComponent::Base + class ConstraintsComponent < Blacklight::Component include Blacklight::ContentAreasShim renders_many :query_constraints_area diff --git a/app/components/blacklight/document/action_component.rb b/app/components/blacklight/document/action_component.rb index fef81e2a0e..61ba315ba7 100644 --- a/app/components/blacklight/document/action_component.rb +++ b/app/components/blacklight/document/action_component.rb @@ -3,7 +3,7 @@ module Blacklight module Document # Render a bookmark widget to bookmark / unbookmark a document - class ActionComponent < ::ViewComponent::Base + class ActionComponent < Blacklight::Component with_collection_parameter :action # @param [Blacklight::Document] document diff --git a/app/components/blacklight/document/actions_component.rb b/app/components/blacklight/document/actions_component.rb index 749e0140a4..791a4a56e8 100644 --- a/app/components/blacklight/document/actions_component.rb +++ b/app/components/blacklight/document/actions_component.rb @@ -3,7 +3,7 @@ module Blacklight module Document # Render a bookmark widget to bookmark / unbookmark a document - class ActionsComponent < ::ViewComponent::Base + class ActionsComponent < Blacklight::Component renders_many :actions, (lambda do |action:, component: nil, **kwargs| component ||= action.component || Blacklight::Document::ActionComponent component.new(action: action, document: @document, options: @options, url_opts: @url_opts, link_classes: @link_classes, **kwargs) diff --git a/app/components/blacklight/document/bookmark_component.rb b/app/components/blacklight/document/bookmark_component.rb index 410f9c3453..d901bbb407 100644 --- a/app/components/blacklight/document/bookmark_component.rb +++ b/app/components/blacklight/document/bookmark_component.rb @@ -3,7 +3,7 @@ module Blacklight module Document # Render a bookmark widget to bookmark / unbookmark a document - class BookmarkComponent < ::ViewComponent::Base + class BookmarkComponent < Blacklight::Component # @param [Blacklight::Document] document # @param [Boolean] checked # @param [Object] bookmark_path the rails route to use for bookmarks diff --git a/app/components/blacklight/document/citation_component.rb b/app/components/blacklight/document/citation_component.rb index fed84c9a64..173cd59613 100644 --- a/app/components/blacklight/document/citation_component.rb +++ b/app/components/blacklight/document/citation_component.rb @@ -3,7 +3,7 @@ module Blacklight module Document # Render citations for the document - class CitationComponent < ::ViewComponent::Base + class CitationComponent < Blacklight::Component DEFAULT_FORMATS = { 'blacklight.citation.mla': :export_as_mla_citation_txt, 'blacklight.citation.apa': :export_as_apa_citation_txt, diff --git a/app/components/blacklight/document/group_component.rb b/app/components/blacklight/document/group_component.rb index 734c70a285..8253f42014 100644 --- a/app/components/blacklight/document/group_component.rb +++ b/app/components/blacklight/document/group_component.rb @@ -3,7 +3,7 @@ module Blacklight module Document # Render the 'more like this' results from the response - class GroupComponent < ::ViewComponent::Base + class GroupComponent < Blacklight::Component with_collection_parameter :group # @param [Blacklight::Solr::Response::Group] group diff --git a/app/components/blacklight/document/more_like_this_component.rb b/app/components/blacklight/document/more_like_this_component.rb index 0b388a3384..8bff9c122f 100644 --- a/app/components/blacklight/document/more_like_this_component.rb +++ b/app/components/blacklight/document/more_like_this_component.rb @@ -3,7 +3,7 @@ module Blacklight module Document # Render the 'more like this' results from the response - class MoreLikeThisComponent < ::ViewComponent::Base + class MoreLikeThisComponent < Blacklight::Component with_collection_parameter :document # @param [Blacklight::Document] document diff --git a/app/components/blacklight/document/thumbnail_component.rb b/app/components/blacklight/document/thumbnail_component.rb index f7363e26cc..8809cb59b8 100644 --- a/app/components/blacklight/document/thumbnail_component.rb +++ b/app/components/blacklight/document/thumbnail_component.rb @@ -3,7 +3,7 @@ module Blacklight module Document # Render the thumbnail for the document - class ThumbnailComponent < ::ViewComponent::Base + class ThumbnailComponent < Blacklight::Component with_collection_parameter :presenter # @param [Blacklight::DocumentPresenter] presenter diff --git a/app/components/blacklight/document_component.rb b/app/components/blacklight/document_component.rb index b0162266b0..a7241958b2 100644 --- a/app/components/blacklight/document_component.rb +++ b/app/components/blacklight/document_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class DocumentComponent < ::ViewComponent::Base + class DocumentComponent < Blacklight::Component include Blacklight::ContentAreasShim # Content appearing before the document diff --git a/app/components/blacklight/document_metadata_component.rb b/app/components/blacklight/document_metadata_component.rb index 03378d27f5..2545dfdf3e 100644 --- a/app/components/blacklight/document_metadata_component.rb +++ b/app/components/blacklight/document_metadata_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class DocumentMetadataComponent < ::ViewComponent::Base + class DocumentMetadataComponent < Blacklight::Component renders_many :fields, (lambda do |component: nil, **kwargs| (component || Blacklight::MetadataFieldComponent).new(**kwargs) end) diff --git a/app/components/blacklight/document_title_component.rb b/app/components/blacklight/document_title_component.rb index 147e7ad093..077e068ce9 100644 --- a/app/components/blacklight/document_title_component.rb +++ b/app/components/blacklight/document_title_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class DocumentTitleComponent < ::ViewComponent::Base + class DocumentTitleComponent < Blacklight::Component renders_many :before_title renders_many :after_title renders_many :actions diff --git a/app/components/blacklight/facet_field_checkboxes_component.rb b/app/components/blacklight/facet_field_checkboxes_component.rb index 7210fd1f6d..168a8e4199 100644 --- a/app/components/blacklight/facet_field_checkboxes_component.rb +++ b/app/components/blacklight/facet_field_checkboxes_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class FacetFieldCheckboxesComponent < ::ViewComponent::Base + class FacetFieldCheckboxesComponent < Blacklight::Component def initialize(facet_field:, layout: nil) @facet_field = facet_field @layout = layout == false ? FacetFieldNoLayoutComponent : Blacklight::FacetFieldComponent diff --git a/app/components/blacklight/facet_field_component.rb b/app/components/blacklight/facet_field_component.rb index a74c2af646..13756d3480 100644 --- a/app/components/blacklight/facet_field_component.rb +++ b/app/components/blacklight/facet_field_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class FacetFieldComponent < ::ViewComponent::Base + class FacetFieldComponent < Blacklight::Component include Blacklight::ContentAreasShim renders_one :label diff --git a/app/components/blacklight/facet_field_filter_component.rb b/app/components/blacklight/facet_field_filter_component.rb index 59324a1963..91abe91953 100644 --- a/app/components/blacklight/facet_field_filter_component.rb +++ b/app/components/blacklight/facet_field_filter_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class FacetFieldFilterComponent < ::ViewComponent::Base + class FacetFieldFilterComponent < Blacklight::Component def initialize(facet_field:) @facet_field = facet_field end diff --git a/app/components/blacklight/facet_field_inclusive_constraint_component.rb b/app/components/blacklight/facet_field_inclusive_constraint_component.rb index bd990142bb..62d2d1be23 100644 --- a/app/components/blacklight/facet_field_inclusive_constraint_component.rb +++ b/app/components/blacklight/facet_field_inclusive_constraint_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class FacetFieldInclusiveConstraintComponent < ::ViewComponent::Base + class FacetFieldInclusiveConstraintComponent < Blacklight::Component with_collection_parameter :facet_field def initialize(facet_field:, values: nil) diff --git a/app/components/blacklight/facet_field_list_component.rb b/app/components/blacklight/facet_field_list_component.rb index 463acecd1a..2a8b19e161 100644 --- a/app/components/blacklight/facet_field_list_component.rb +++ b/app/components/blacklight/facet_field_list_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class FacetFieldListComponent < ::ViewComponent::Base + class FacetFieldListComponent < Blacklight::Component def initialize(facet_field:, layout: nil) @facet_field = facet_field @layout = layout == false ? FacetFieldNoLayoutComponent : Blacklight::FacetFieldComponent diff --git a/app/components/blacklight/facet_field_no_layout_component.rb b/app/components/blacklight/facet_field_no_layout_component.rb index c77c89c236..c99f72c3b1 100644 --- a/app/components/blacklight/facet_field_no_layout_component.rb +++ b/app/components/blacklight/facet_field_no_layout_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class FacetFieldNoLayoutComponent < ::ViewComponent::Base + class FacetFieldNoLayoutComponent < Blacklight::Component include Blacklight::ContentAreasShim renders_one :label diff --git a/app/components/blacklight/facet_field_pagination_component.rb b/app/components/blacklight/facet_field_pagination_component.rb index 93a719ccf7..c4e7cedd6f 100644 --- a/app/components/blacklight/facet_field_pagination_component.rb +++ b/app/components/blacklight/facet_field_pagination_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class FacetFieldPaginationComponent < ::ViewComponent::Base + class FacetFieldPaginationComponent < Blacklight::Component def initialize(facet_field:) @facet_field = facet_field end diff --git a/app/components/blacklight/facet_item_component.rb b/app/components/blacklight/facet_item_component.rb index 566a70f42a..e9f8618485 100644 --- a/app/components/blacklight/facet_item_component.rb +++ b/app/components/blacklight/facet_item_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class FacetItemComponent < ::ViewComponent::Base + class FacetItemComponent < Blacklight::Component extend Deprecation with_collection_parameter :facet_item diff --git a/app/components/blacklight/facet_item_pivot_component.rb b/app/components/blacklight/facet_item_pivot_component.rb index 6c91bdb4ce..3687549259 100644 --- a/app/components/blacklight/facet_item_pivot_component.rb +++ b/app/components/blacklight/facet_item_pivot_component.rb @@ -2,7 +2,7 @@ module Blacklight # Render facet items and any subtree - class FacetItemPivotComponent < ::ViewComponent::Base + class FacetItemPivotComponent < Blacklight::Component # Somewhat arbitrary number; the only important thing is that # it is bigger than the number of leaf nodes in any collapsing # pivot facet on the page. diff --git a/app/components/blacklight/hidden_search_state_component.rb b/app/components/blacklight/hidden_search_state_component.rb index 4bda030dcb..eb3dd224b1 100644 --- a/app/components/blacklight/hidden_search_state_component.rb +++ b/app/components/blacklight/hidden_search_state_component.rb @@ -4,7 +4,7 @@ module Blacklight # Writes out zero or more elements, completely # representing a hash passed in using Rails-style request parameters # for hashes nested with arrays and other hashes. - class HiddenSearchStateComponent < ::ViewComponent::Base + class HiddenSearchStateComponent < Blacklight::Component # @param [Hash] params def initialize(params:) Deprecation.warn(self, "Passing page as a parameter to HiddenSearchStateComponent is deprecated and will not be supported in Blacklight 8") if params.key?(:page) diff --git a/app/components/blacklight/metadata_field_component.rb b/app/components/blacklight/metadata_field_component.rb index f128ef37ee..47c3ef311b 100644 --- a/app/components/blacklight/metadata_field_component.rb +++ b/app/components/blacklight/metadata_field_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class MetadataFieldComponent < ::ViewComponent::Base + class MetadataFieldComponent < Blacklight::Component with_collection_parameter :field # @param field [Blacklight::FieldPresenter] diff --git a/app/components/blacklight/metadata_field_layout_component.rb b/app/components/blacklight/metadata_field_layout_component.rb index c332493135..51acc5f221 100644 --- a/app/components/blacklight/metadata_field_layout_component.rb +++ b/app/components/blacklight/metadata_field_layout_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class MetadataFieldLayoutComponent < ::ViewComponent::Base + class MetadataFieldLayoutComponent < Blacklight::Component include Blacklight::ContentAreasShim with_collection_parameter :field diff --git a/app/components/blacklight/response/facet_group_component.rb b/app/components/blacklight/response/facet_group_component.rb index d5fa9034fc..270987898f 100644 --- a/app/components/blacklight/response/facet_group_component.rb +++ b/app/components/blacklight/response/facet_group_component.rb @@ -3,7 +3,7 @@ module Blacklight module Response # Render a group of facet fields - class FacetGroupComponent < ::ViewComponent::Base + class FacetGroupComponent < Blacklight::Component # @param [Blacklight::Response] response # @param [Array] fields facet fields to render # @param [String] title the title of the facet group section diff --git a/app/components/blacklight/response/pagination_component.rb b/app/components/blacklight/response/pagination_component.rb index 4c35f1b501..f0038fad36 100644 --- a/app/components/blacklight/response/pagination_component.rb +++ b/app/components/blacklight/response/pagination_component.rb @@ -3,7 +3,7 @@ module Blacklight module Response # Render a pagination widget for search results - class PaginationComponent < ::ViewComponent::Base + class PaginationComponent < Blacklight::Component # @param [Blacklight::Response] response # @param [Hash] html html options for the pagination container def initialize(response:, html: {}, **pagination_args) diff --git a/app/components/blacklight/search_bar_component.rb b/app/components/blacklight/search_bar_component.rb index 998d81a439..d5b73b07e5 100644 --- a/app/components/blacklight/search_bar_component.rb +++ b/app/components/blacklight/search_bar_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class SearchBarComponent < ::ViewComponent::Base + class SearchBarComponent < Blacklight::Component include Blacklight::ContentAreasShim renders_one :append diff --git a/app/components/blacklight/search_context_component.rb b/app/components/blacklight/search_context_component.rb index 46a8a43fe0..210b51363e 100644 --- a/app/components/blacklight/search_context_component.rb +++ b/app/components/blacklight/search_context_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class SearchContextComponent < ::ViewComponent::Base + class SearchContextComponent < Blacklight::Component with_collection_parameter :search_context def initialize(search_context:, search_session:) diff --git a/app/components/blacklight/start_over_button_component.rb b/app/components/blacklight/start_over_button_component.rb index cb0dcfa0fb..3385cbfe77 100644 --- a/app/components/blacklight/start_over_button_component.rb +++ b/app/components/blacklight/start_over_button_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Blacklight - class StartOverButtonComponent < ::ViewComponent::Base + class StartOverButtonComponent < Blacklight::Component def call link_to t('blacklight.search.start_over'), start_over_path, class: 'catalog_startOverLink btn btn-primary' end diff --git a/lib/blacklight.rb b/lib/blacklight.rb index 9709668ed2..4e280b7389 100644 --- a/lib/blacklight.rb +++ b/lib/blacklight.rb @@ -7,6 +7,7 @@ module Blacklight autoload :AbstractRepository, 'blacklight/abstract_repository' + autoload :Component, 'blacklight/component' autoload :Configuration, 'blacklight/configuration' autoload :Exceptions, 'blacklight/exceptions' autoload :Parameters, 'blacklight/parameters' diff --git a/lib/blacklight/component.rb b/lib/blacklight/component.rb new file mode 100644 index 0000000000..8aab8e112f --- /dev/null +++ b/lib/blacklight/component.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Blacklight + class Component < ViewComponent::Base + class << self + # rubocop:disable Naming/MemoizedInstanceVariableName + def compiler + @__vc_compiler ||= EngineCompiler.new(self) + end + # rubocop:enable Naming/MemoizedInstanceVariableName + end + + class EngineCompiler < ::ViewComponent::Compiler + # ViewComponent::Compiler locates and caches templates from sidecar files to the component source file. + # While this is sensible in a Rails application, it prevents component templates defined in an Engine + # from being overridden by an installing application without subclassing the component, which may also + # require modifying any partials rendering the component. This subclass of compiler overrides the template + # location algorithm to take the sidecar file names from the Engine, but look to see if a file of the + # same name existing in the installing application (ie, under Rails.root). If the latter exists, this + # compiler will cache that template instead of the engine-defined file; if not, the compiler will fall + # back to the engine-defined file. + def templates + @templates ||= begin + extensions = ActionView::Template.template_handler_extensions + + component_class._sidecar_files(extensions).each_with_object([]) do |path, memo| + pieces = File.basename(path).split(".") + app_path = "#{Rails.root}/#{path.slice(path.index(component_class.view_component_path)..-1)}" + + memo << { + path: File.exist?(app_path) ? app_path : path, + variant: pieces.second.split("+").second&.to_sym, + handler: pieces.last + } + end + end + end + end + end +end diff --git a/spec/lib/blacklight/component_spec.rb b/spec/lib/blacklight/component_spec.rb new file mode 100644 index 0000000000..12c2bbc6eb --- /dev/null +++ b/spec/lib/blacklight/component_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +RSpec.describe Blacklight::Component do + let(:component_class) { Blacklight::DocumentTitleComponent } + + context "subclassed" do + it "returns our Compiler implementation" do + expect(component_class.ancestors).to include described_class + expect(component_class.compiler).to be_a Blacklight::Component::EngineCompiler + end + end + + describe Blacklight::Component::EngineCompiler do + subject(:compiler) { described_class.new(component_class) } + + let(:original_compiler) { ViewComponent::Compiler.new(component_class) } + let(:original_path) { original_compiler.send(:templates).first[:path] } + let(:resolved_path) { compiler.templates.first[:path] } + + context "without overrides" do + it "links to engine template" do + expect(resolved_path).not_to include(".internal_test_app") + expect(resolved_path).to eql(original_path) + end + end + + context "with overrides" do + let(:path_match) do + Regexp.new(Regexp.escape(File.join(".internal_test_app", component_class.view_component_path))) + end + + before do + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with(path_match).and_return(true) + end + + it "links to application template" do + expect(resolved_path).to include(".internal_test_app") + expect(resolved_path).not_to eql(original_path) + end + end + end +end