In Rails 7, Turbolinks is replaced by Hotwire / Turbo. This patches up web links so that they become AJAX-style requests just like Turbolinks did, but also patches up forms. On the whole, we found this broke swathes of our application.
flash
and render :new
/render :edit
" - break, astonishingly; one must add status: :unprocessable_entity
and cross fingers....#foo
), Turbo strips it.In the end, there's only so much data: {turbo: false}
it makes sense to scatter all over your code base. Worse, we're using SimpleForm, so this becomes the even-more-cumbersome html: {data: {turbo: false}}
.
Is there a way to _globally disable Turbo just for forms (ideally all forms, whatever their origin - just leave the <form>
tag and everything inside it completely alone please) but leave the rest of its behaviour unchanged?
Wish granted, although, undocumented. Available since turbo v7.2.0 (turbo-rails v1.3.0).
// app/javascript/application.js
Turbo.setFormMode("on" | "off" | "optin")
// for turbo v8.0.6+
Turbo.config.forms.mode = "on" | "off" | "optin"
"on"
- default - Turbo forms all the time. Use data-turbo="false"
to disable turbo on individual forms.
"off"
- No turbo forms ever. data-turbo="true"
is ignored.
"optin"
- No turbo forms unless you insist. Use data-turbo="true"
to enable turbo on individual forms.
https://github.com/hotwired/turbo/pull/419
I posted this Q&A on Stack Overflow as I couldn't find any other solutions out there. Clearly, there are numerous shortcomings and I would much prefer to see a simple configuration flag for Turbo that would make it ignore forms - this would be an overwhelmingly preferable approach.
I still don't have a full solution either way, because forms generated internally by Rails for e.g. a link_to(...method: delete)
are still a problem, but I did work around some of it using some monkey patching.
On the one hand there are Rails forms:
data-turbo
attribute's value apples to that node and all children so one can wrap e.g. a Rails-generated 'dynamic' form from e.g. a link_to(...method: delete)
in a DIV with that attribute set and, at least, work around those problems on a case-by-case basis - though note, I'm having trouble making this work in some cases still.On the other hand there are SimpleForm forms:
form
elements that it constructs. Previous requests for this in GitHub issues have so far been explicitly refused.I happened to be involved in a gem called Hoodoo that provides monkey patching facilities that lets you write patch modules which are more like subclasses - super
is available to call up to the patched implementation - and, further, patches can be enabled or disabled dynamically and easily. Hoodoo is actually a Rack application service framework, so this is something of a sledgehammer - I always intended to one day extract this into its own gem, but at the time of writing, I have not got around to it (and several years have gone by) - but we can require
just the part we need and ignore the rest.
Here, I patch SimpleForm's builder methods. These just call Rails' form helpers under the hood, so I might have a go at patching lower down in Rails instead, but anyway, the following worked.
In your Gemfile
, declare the Hoodoo dependency but don't load all of its component parts as you won't want most of them.
# Hoodoo's monkey patch module is useful sometimes:
# https://rubygems.org/gems/hoodoo
#
# MUST use 'require: false' so that the Rack components of Hoodoo middleware
# do not get activated; this is a Rails app, not a Hoodoo service!
#
gem 'hoodoo', '~> 2.12', require: false
...then write something like config/initializers/simple_form_monkey_patch.rb
which looks something like this:
require 'hoodoo/monkey'
module SimpleFormMonkey
module InstanceExtensions
def simple_form_for(record, options = {}, &block)
modified_options = {html: {data: {turbo: false}}}
modified_options.deep_merge!(options)
super(record, modified_options, &block)
end
end
end
Hoodoo::Monkey.register(
extension_module: SimpleFormMonkey,
target_unit: SimpleForm::ActionViewExtensions::FormHelper
)
Hoodoo::Monkey.enable(
extension_module: SimpleFormMonkey
)
...that'll do it. This has some risk as we're patching things which are - in terms of the module name & nesting - technically private to SimpleForm, but the method signature itself is at least public. You could patch ActionView::Helpers::FormHelper
with an override for form_for
instead, if you wanted to go lower level and patch an API that's been stable for a very long time. The code would be almost identical as the method signatures are the same.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With