Module: TurboOverlay::Controller

Extended by:
ActiveSupport::Concern
Defined in:
lib/turbo_overlay/controller.rb

Overview

Controller concern. Include in ApplicationController (or any controller you want overlay-aware):

class ApplicationController < ActionController::Base
include TurboOverlay::Controller
end

The concern auto-installs a layout proc that swaps in the matching overlay layout for overlay requests and preserves "turbo_rails/frame" for plain turbo-frame requests (so including this concern does not regress Turbo's frame-layout optimization). For apps with a custom layout method, call turbo_overlay_layout from your method — see the README "A note on custom layouts" section.

Constant Summary collapse

OVERLAY_FRAME_PREFIX =
"turbo_overlay_".freeze
OVERLAY_TYPE_HEADER =
"X-Turbo-Overlay".freeze
OVERLAY_ID_HEADER =
"X-Turbo-Overlay-Id".freeze
OVERLAY_POSITION_HEADER =
"X-Turbo-Overlay-Position".freeze
OVERLAY_ALIGN_HEADER =
"X-Turbo-Overlay-Align".freeze
OVERLAY_OFFSET_HEADER =
"X-Turbo-Overlay-Offset".freeze
OVERLAY_BACKDROP_HEADER =
"X-Turbo-Overlay-Backdrop".freeze
OVERLAY_CLOSE_HEADER =
"X-Turbo-Overlay-Close".freeze
OVERLAY_KEEP_OPEN_HEADER =
"X-Turbo-Overlay-Keep-Open".freeze
ALLOWED_POSITIONS =

Whitelists for values that originate from request headers and get reflected into rendered chrome (CSS class tokens, DOM ids, frame names). Constraining them at the resolver protects every downstream consumer — gem partials, generator templates, JS data attributes — without each having to re-validate.

%i[left right top bottom].freeze
ALLOWED_ALIGNS =
%i[start center end].freeze
OVERLAY_ID_FORMAT =
/\A[A-Za-z0-9_-]{1,64}\z/.freeze
OFFSET_RANGE =
(-10_000..10_000).freeze

Instance Method Summary collapse

Instance Method Details

#drawer_request?Boolean

----- drawer -----



68
69
70
# File 'lib/turbo_overlay/controller.rb', line 68

def drawer_request?
  turbo_overlay_type == :drawer
end

#hint_request?Boolean

----- hint -----



80
81
82
# File 'lib/turbo_overlay/controller.rb', line 80

def hint_request?
  turbo_overlay_type == :hint
end

----- modal -----



62
63
64
# File 'lib/turbo_overlay/controller.rb', line 62

def modal_request?
  turbo_overlay_type == :modal
end

#overlay_hintable_request?Boolean

True if the current request will use the hint template — either a hover prefetch (which the gem's JS extracts the template from) or an explicit :hint variant fetch. Gates the +hint variant auto-render in overlay_stack_tag.



132
133
134
# File 'lib/turbo_overlay/controller.rb', line 132

def overlay_hintable_request?
  hint_request? || overlay_prefetch_request?
end

#overlay_prefetch_request?Boolean

True if the current request is a Turbo hover prefetch. Detected via the X-Sec-Purpose: prefetch request header that Turbo sends — the W3C Sec-Purpose is a Forbidden Header for JS-initiated fetch() requests, so Turbo prepends X-. Used by overlay_stack_tag to decide whether to render the action's +hint variant template inline.



123
124
125
126
# File 'lib/turbo_overlay/controller.rb', line 123

def overlay_prefetch_request?
  return false unless respond_to?(:request) && request
  request.headers["X-Sec-Purpose"].to_s.include?("prefetch")
end

#overlay_request?Boolean

True if the current request targets any configured overlay (initial open or in-overlay form re-render). Useful in shared partials.



113
114
115
# File 'lib/turbo_overlay/controller.rb', line 113

def overlay_request?
  !turbo_overlay_type.nil?
end

#popover_request?Boolean

----- popover -----



74
75
76
# File 'lib/turbo_overlay/controller.rb', line 74

def popover_request?
  turbo_overlay_type == :popover
end

#turbo_overlay_alignObject

The per-link cross-axis alignment for popovers, parsed from the X-Turbo-Overlay-Align header. Returns a Symbol (:start, :center, :end) or nil. Popover partials fall back to TurboOverlay.configuration.popover.align.



177
178
179
180
# File 'lib/turbo_overlay/controller.rb', line 177

def turbo_overlay_align
  return @_turbo_overlay_align if defined?(@_turbo_overlay_align)
  @_turbo_overlay_align = _resolve_overlay_align
end

#turbo_overlay_backdrop?Boolean

Whether the current overlay request should render with a backdrop. Defaults to true; only false when the link helper explicitly passed backdrop: false (carried in the X-Turbo-Overlay-Backdrop header). Drawer partials switch the <dialog> open mode and CSS based on this.



196
197
198
199
# File 'lib/turbo_overlay/controller.rb', line 196

def turbo_overlay_backdrop?
  return @_turbo_overlay_backdrop if defined?(@_turbo_overlay_backdrop)
  @_turbo_overlay_backdrop = _resolve_overlay_backdrop
end

#turbo_overlay_close?Boolean

Whether the current overlay request should render the chrome's default close ("×") button. Defaults to true; only false when the link helper explicitly passed close: false (carried in the X-Turbo-Overlay-Close header). Chrome partials consult overlay_close? (view helper) which folds this into the full opt-out precedence chain.



207
208
209
210
# File 'lib/turbo_overlay/controller.rb', line 207

def turbo_overlay_close?
  return @_turbo_overlay_close if defined?(@_turbo_overlay_close)
  @_turbo_overlay_close = _resolve_overlay_close
end

#turbo_overlay_frame_re_render?Boolean

True when this is a form/link response targeting an existing overlay's turbo-frame (form re-render in place). Mirrors the same prefetch guard as _resolve_overlay_type: a hover prefetch carries the enclosing frame's id in Turbo-Frame but is NOT a re-render — treating it as one would flip the response Content-Type to text/vnd.turbo-stream.html (via _turbo_overlay_set_stream_content_type) for a body that's a plain <turbo-frame>, an incoherent mismatch.



241
242
243
244
245
# File 'lib/turbo_overlay/controller.rb', line 241

def turbo_overlay_frame_re_render?
  return false unless respond_to?(:request) && request
  return false if overlay_prefetch_request?
  request.headers["Turbo-Frame"].to_s.start_with?(OVERLAY_FRAME_PREFIX)
end

#turbo_overlay_idObject

The overlay id for the current request. Resolution order:

  1. X-Turbo-Overlay-Id request header (caller supplied overlay_id: on the link helper)
  2. The <id> segment parsed from a Turbo-Frame: turbo_overlay_<type>_<id> header (form re-render)
  3. A freshly generated SecureRandom.alphanumeric(8) id, memoized for the duration of the request

Available in the controller and in views (e.g. for turbo_stream.overlay(:close, id: turbo_overlay_id)).



156
157
158
159
# File 'lib/turbo_overlay/controller.rb', line 156

def turbo_overlay_id
  return @_turbo_overlay_id if defined?(@_turbo_overlay_id)
  @_turbo_overlay_id = _resolve_overlay_id
end

#turbo_overlay_initial_open?Boolean

True for the initial open of an overlay (an X-Turbo-Overlay request that is not a form re-render inside an existing overlay frame). Used internally to decide between turbo-stream append wrapping and turbo-frame replace wrapping.



228
229
230
231
# File 'lib/turbo_overlay/controller.rb', line 228

def turbo_overlay_initial_open?
  return false unless turbo_overlay_type
  !turbo_overlay_frame_re_render?
end

#turbo_overlay_keep_open_on_redirect?Boolean

Whether the overlay should stay open when a form descendant submits and the response is a redirect. Defaults to false (close-on-redirect is the gem's default); becomes true when the trigger link passed keep_overlay_open_on_redirect: true (carried in the X-Turbo-Overlay-Keep-Open header). The chrome partials reflect this onto the <dialog> as a data attribute the per-dialog submit-end listener reads.



219
220
221
222
# File 'lib/turbo_overlay/controller.rb', line 219

def turbo_overlay_keep_open_on_redirect?
  return @_turbo_overlay_keep_open if defined?(@_turbo_overlay_keep_open)
  @_turbo_overlay_keep_open = _resolve_overlay_keep_open_on_redirect
end

#turbo_overlay_layoutObject

Layout name for the current request. Returns the matching overlay layout for overlay requests, "turbo_rails/frame" for plain turbo-frame requests (preserving Turbo's optimization, since our auto-installed layout proc replaces Turbo's), or nil otherwise so Rails picks the default layout.

Apps with a custom layout method should call this first:

def custom_layout
turbo_overlay_layout || "my_app_layout"
end


97
98
99
100
101
102
103
104
105
106
# File 'lib/turbo_overlay/controller.rb', line 97

def turbo_overlay_layout
  case turbo_overlay_type
  when :modal   then "turbo_overlay/modal"
  when :drawer  then "turbo_overlay/drawer"
  when :popover then "turbo_overlay/popover"
  when :hint    then "turbo_overlay/hint"
  else
    "turbo_rails/frame" if respond_to?(:turbo_frame_request?) && turbo_frame_request?
  end
end

#turbo_overlay_offsetObject

The per-link pixel offset between trigger and popover, parsed from the X-Turbo-Overlay-Offset header. Returns an Integer or nil. Popover partials fall back to TurboOverlay.configuration.popover.offset.



186
187
188
189
# File 'lib/turbo_overlay/controller.rb', line 186

def turbo_overlay_offset
  return @_turbo_overlay_offset if defined?(@_turbo_overlay_offset)
  @_turbo_overlay_offset = _resolve_overlay_offset
end

#turbo_overlay_positionObject

The per-link position override for the current overlay request, parsed from the X-Turbo-Overlay-Position header. Returns a Symbol (:left, :right, :top, :bottom) or nil when the link didn't supply one. Drawer partials use this with a fallback to TurboOverlay.configuration.drawer.position; popover partials use it with a fallback to TurboOverlay.configuration.popover.position.



168
169
170
171
# File 'lib/turbo_overlay/controller.rb', line 168

def turbo_overlay_position
  return @_turbo_overlay_position if defined?(@_turbo_overlay_position)
  @_turbo_overlay_position = _resolve_overlay_position
end

#turbo_overlay_typeObject

Returns :modal, :drawer, :popover, or nil. Detected from the X-Turbo-Overlay request header (initial open) or the Turbo-Frame: turbo_overlay_<type>_<id> header (form re-render inside an open overlay).



140
141
142
143
# File 'lib/turbo_overlay/controller.rb', line 140

def turbo_overlay_type
  return @_turbo_overlay_type if defined?(@_turbo_overlay_type)
  @_turbo_overlay_type = _resolve_overlay_type
end