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 —–

Returns:

  • (Boolean)


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

def drawer_request?
  turbo_overlay_type == :drawer
end

#hint_request?Boolean

—– hint —–

Returns:

  • (Boolean)


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

def hint_request?
  turbo_overlay_type == :hint
end

—– modal —–

Returns:

  • (Boolean)


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`.

Returns:

  • (Boolean)


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.

Returns:

  • (Boolean)


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.

Returns:

  • (Boolean)


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

def overlay_request?
  !turbo_overlay_type.nil?
end

#popover_request?Boolean

—– popover —–

Returns:

  • (Boolean)


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.

Returns:

  • (Boolean)


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.

Returns:

  • (Boolean)


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.

Returns:

  • (Boolean)


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.

Returns:

  • (Boolean)


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.

Returns:

  • (Boolean)


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