app/models/coupdoeil/popover.rb
# frozen_string_literal: true module Coupdoeil class Popover < AbstractController::Base abstract! include AbstractController::Rendering include AbstractController::Logger include AbstractController::Helpers include AbstractController::Translation include AbstractController::Callbacks include AbstractController::Caching include ActionView::Layouts include ActionView::Rendering include Rails.application.routes.url_helpers layout "popover" # so coupdoeil helpers are always available within popovers helper Coupdoeil::ApplicationHelper @registry = Registry.new @default_options_by_method = {}.with_indifferent_access DEFAULT_OPTIONS_KEY = Object.new private_constant :DEFAULT_OPTIONS_KEY @default_options_by_method[DEFAULT_OPTIONS_KEY] = OptionsSet.new( offset: 0, placement: "auto", animation: "slide-in", cache: true, loading: :async, trigger: "hover", opening_delay: true, ) DoubleRenderError = Class.new(::AbstractController::DoubleRenderError) # See engine initialization for view paths class << self attr_reader :registry def popover_resource_name = @popover_resource_name ||= name.delete_suffix("Popover").underscore def with(...) = setup_class.new(self).with_params(...) def inherited(subclass) super Coupdoeil::Popover.registry.register(subclass.popover_resource_name, subclass) subclass.instance_variable_set(:@default_options_by_method, @default_options_by_method.dup) end def default_options(...) = default_options_for(DEFAULT_OPTIONS_KEY, ...) def default_options_for(*action_names, **option_values) if option_values.blank? @default_options_by_method[action_names.first] || default_options else action_names.each do |action_name| options = @default_options_by_method[action_name] || default_options @default_options_by_method[action_name] = options.merge(OptionsSet.new(option_values)) end end end def method_missing(method_name, *args, &) return super unless action_methods.include?(method_name.name) action_methods.each do |action_name| define_singleton_method(action_name) { setup_class.new(self).with_type(action_name) } end public_send(method_name) end def respond_to_missing?(method, include_all = false) action_methods.include?(method.name) || super end def setup_class @setup_class ||= begin setup_klass = Class.new(Setup) action_methods.each do |action_name| setup_klass.define_method(action_name) { with_type(action_name) } end setup_klass end end end attr_reader :params def initialize(params, cp_view_context) super() @params = params @__cp_view_context = cp_view_context end def helpers = @__cp_view_context def controller = @__cp_view_context.controller def view_context super.tap do |context| context.extend(ViewContextDelegation) context.popover = self context.__cp_view_context = @__cp_view_context end end def render(...) return super unless response_body raise DoubleRenderError, "Render was called multiple times in this action. \ Also note that render does not terminate execution of the action." end def process(method_name, *) benchmark("processed popover #{self.class.popover_resource_name}/#{method_name}", silence: true) do ActiveSupport::Notifications.instrument("render_popover.coupdoeil") do super response_body || render(action_name) end end end end end