Module: TurboCrud::Helpers

Defined in:
lib/turbo_crud/helpers.rb

Instance Method Summary collapse

Instance Method Details

#turbo_crud_container(title:, container: nil, model: nil, close_to_top: true, &block) ⇒ Object

Wrap any existing view inside a TurboCrud container (modal or drawer).

Usage (in your existing new/edit):

<%= turbo_crud_container title: "New Post" do %>
<%= render "form" %>
<% end %>

The container defaults to TurboCrud.config.default_container (:modal or :drawer), but you can force one:

turbo_crud_container(title: "...", container: :drawer) { ... }


202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/turbo_crud/helpers.rb', line 202

def turbo_crud_container(title:, container: nil, model: nil, close_to_top: true, &block)
  # Capture the block output (the form / content you already have).
  body = capture(&block)

  chosen =
    if container
      container.to_sym
    else
      # Auto-select container from the incoming Turbo frame request.
      case turbo_crud_requested_frame_id
      when TurboCrud.config.drawer_frame_id then :drawer
      when TurboCrud.config.modal_frame_id then :modal
      else
        turbo_crud_default_container_for(model)
      end
    end

  # We render a partial shipped by the gem, because HTML is nicer to read there.
  partial =
    case chosen
    when :drawer then "turbo_crud/shared/container_drawer"
    else "turbo_crud/shared/container_modal"
    end

  frame_id = chosen == :drawer ? TurboCrud.config.drawer_frame_id : TurboCrud.config.modal_frame_id

  turbo_frame_tag(frame_id) do
    render partial, title: title, body: body, close_to_top: close_to_top
  end
end

#turbo_crud_default_frame_id(model_or_record = nil) ⇒ Object

Decide the default container frame for forms based on configuration. If you set default_container = :drawer, forms will target the drawer.



170
171
172
173
174
175
176
177
178
# File 'lib/turbo_crud/helpers.rb', line 170

def turbo_crud_default_frame_id(model_or_record = nil)
  # Prefer the frame requested by the current Turbo visit so drawer/modal
  # links and form targets stay aligned in existing apps.
  requested = turbo_crud_requested_frame_id
  return requested if [TurboCrud.config.modal_frame_id, TurboCrud.config.drawer_frame_id].include?(requested)

  container = turbo_crud_default_container_for(model_or_record)
  container == :drawer ? TurboCrud.config.drawer_frame_id : TurboCrud.config.modal_frame_id
end

#turbo_crud_drawer_frame ⇒ Object

Drawer frame: like a modal, but it slides in from the side. 🧊➡️



145
146
147
148
149
150
# File 'lib/turbo_crud/helpers.rb', line 145

def turbo_crud_drawer_frame
  safe_join([
    turbo_frame_tag(TurboCrud.config.drawer_frame_id),
    turbo_crud_behavior_script_once
  ])
end

Drawer link: opens URL inside drawer frame.



161
162
163
164
165
166
# File 'lib/turbo_crud/helpers.rb', line 161

def turbo_crud_drawer_link(text, url, **options)
  options[:data] ||= {}
  options[:data][:turbo_frame] = TurboCrud.config.drawer_frame_id
  options[:class] ||= "turbo-crud__drawer-link"
  link_to(text, url, **options)
end

#turbo_crud_flash_frame ⇒ Object

Flash frame: Turbo Streams can replace this for instant feedback.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/turbo_crud/helpers.rb', line 17

def turbo_crud_flash_frame
  frame_classes = ["turbo-crud__flash-frame", turbo_crud_flash_position_class]
  safe_join([
    turbo_frame_tag(
      TurboCrud.config.flash_frame_id,
      class: frame_classes.join(" "),
      data: {
        controller: "turbo-crud-flash",
        turbo_crud_flash_auto_hide_ms_value: TurboCrud.config.flash_auto_hide_ms
      }.compact
    ) do
      turbo_crud_render_flash
    end,
    turbo_crud_behavior_script_once
  ])
end

#turbo_crud_flash_icon(level) ⇒ Object



116
117
118
119
120
121
122
123
# File 'lib/turbo_crud/helpers.rb', line 116

def turbo_crud_flash_icon(level)
  case level.to_sym
  when :notice, :success then "✓"
  when :alert, :error then "!"
  when :warning then "!"
  else "i"
  end
end

#turbo_crud_flash_level_class(level) ⇒ Object



107
108
109
110
111
112
113
114
# File 'lib/turbo_crud/helpers.rb', line 107

def turbo_crud_flash_level_class(level)
  case level.to_sym
  when :notice, :success then "turbo-crud__flash--notice"
  when :alert, :error then "turbo-crud__flash--alert"
  when :warning then "turbo-crud__flash--warning"
  else "turbo-crud__flash--notice"
  end
end

#turbo_crud_flash_messages(notice: nil, alert: nil, source_flash: nil) ⇒ Object

Build normalized flash messages for rendering.



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/turbo_crud/helpers.rb', line 35

def turbo_crud_flash_messages(notice: nil, alert: nil, source_flash: nil)
  messages = []

  messages << { level: :notice, text: notice.to_s } if notice.present?
  messages << { level: :alert, text: alert.to_s } if alert.present?

  # If explicit locals were passed, don't merge session flash to avoid stale leakage.
  return messages if notice || alert

  flash_source = source_flash || flash
  level_map = TurboCrud.config.flash_levels || {}

  level_map.each do |source_key, level|
    value = flash_source[source_key]
    next if value.blank?

    messages << { level: level.to_sym, text: value.to_s }
  end

  messages.uniq
end

#turbo_crud_flash_position_class ⇒ Object



125
126
127
128
129
130
131
132
133
134
# File 'lib/turbo_crud/helpers.rb', line 125

def turbo_crud_flash_position_class
  case TurboCrud.config.flash_position.to_sym
  when :top_center
    "turbo-crud__flash-frame--top-center"
  when :inline
    "turbo-crud__flash-frame--inline"
  else
    "turbo-crud__flash-frame--top-right"
  end
end

#turbo_crud_form_with(*args, frame: nil, **kwargs, &block) ⇒ Object

Wrapper around form_with that targets the correct turbo frame.



181
182
183
184
185
186
187
188
189
190
191
# File 'lib/turbo_crud/helpers.rb', line 181

def turbo_crud_form_with(*args, frame: nil, **kwargs, &block)
  model_for_defaults = turbo_crud_model_from_form_args(args, kwargs)
  resolved_frame = frame || turbo_crud_default_frame_id(model_for_defaults)

  kwargs[:data] ||= {}
  kwargs[:data][:turbo_frame] ||= resolved_frame
  # Auto-disable submit controls while request is in flight (opt out by passing
  # data: { turbo_crud_auto_disable: false } on a specific form).
  kwargs[:data][:turbo_crud_auto_disable] = true unless kwargs[:data].key?(:turbo_crud_auto_disable)
  form_with(*args, **kwargs, &block)
end

#turbo_crud_message_for_level(messages, *levels) ⇒ Object



95
96
97
98
# File 'lib/turbo_crud/helpers.rb', line 95

def turbo_crud_message_for_level(messages, *levels)
  match = messages.find { |message| levels.include?(message[:level].to_sym) }
  match && match[:text]
end

#turbo_crud_modal_frame ⇒ Object

Modal frame: new/edit pages can render inside here.



137
138
139
140
141
142
# File 'lib/turbo_crud/helpers.rb', line 137

def turbo_crud_modal_frame
  safe_join([
    turbo_frame_tag(TurboCrud.config.modal_frame_id),
    turbo_crud_behavior_script_once
  ])
end

Modal link: opens URL inside modal frame.



153
154
155
156
157
158
# File 'lib/turbo_crud/helpers.rb', line 153

def turbo_crud_modal_link(text, url, **options)
  options[:data] ||= {}
  options[:data][:turbo_frame] = TurboCrud.config.modal_frame_id
  options[:class] ||= "turbo-crud__modal-link"
  link_to(text, url, **options)
end

#turbo_crud_render_flash(notice: nil, alert: nil, messages: nil) ⇒ Object

Render flash payload using configured renderer.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/turbo_crud/helpers.rb', line 58

def turbo_crud_render_flash(notice: nil, alert: nil, messages: nil)
  normalized = messages || turbo_crud_flash_messages(notice: notice, alert: alert)
  renderer = TurboCrud.config.flash_renderer

  if renderer.respond_to?(:call)
    return renderer.call(self, messages: normalized)
  end

  return "".html_safe if normalized.blank?

  locals = {
    messages: normalized,
    notice: turbo_crud_message_for_level(normalized, :notice, :success),
    alert: turbo_crud_message_for_level(normalized, :alert, :error, :warning)
  }

  partial =
    case renderer
    when :off, :none
      return "".html_safe
    when :app, :rails_default
      "shared/flash"
    when nil, :default then "turbo_crud/shared/flash"
    else renderer.to_s
    end

  rendered = render partial: partial, locals: locals
  return turbo_crud_wrap_app_flash(rendered) if i[app rails_default].include?(renderer)

  rendered
rescue ActionView::MissingTemplate
  return "".html_safe if i[off none app rails_default].include?(renderer)

  # For explicit custom paths, fall back to safe default.
  render partial: "turbo_crud/shared/flash", locals: locals
end

#turbo_crud_wrap_app_flash(rendered) ⇒ Object



100
101
102
103
104
105
# File 'lib/turbo_crud/helpers.rb', line 100

def turbo_crud_wrap_app_flash(rendered)
  html = rendered.to_s
  return rendered if html.include?("turbo-crud__flash-stack")

  tag.div(rendered, class: "turbo-crud__flash-stack", aria: { live: "polite", atomic: "true" })
end

#turbo_list_id(klass_or_relation) ⇒ Object

Example: turbo_list_id(Post) => "posts_list"



6
7
8
9
# File 'lib/turbo_crud/helpers.rb', line 6

def turbo_list_id(klass_or_relation)
  klass = klass_or_relation.respond_to?(:klass) ? klass_or_relation.klass : klass_or_relation
  "#{klass.model_name.plural}_list"
end

#turbo_row_id(record) ⇒ Object

Example: turbo_row_id(@post) => "post_123"



12
13
14
# File 'lib/turbo_crud/helpers.rb', line 12

def turbo_row_id(record)
  dom_id(record)
end