If you're using a form from Sender.net to collect e-mail subscribers, you may find it doesn't behave well in a Rails app with Turbo installed.
Sender.net provides a tool in its dashboard for building and formatting signup forms, but it doesn't create HTML for you to insert into your web site; it creates javascript, which pulls the data from their site and puts it into an iframe. Like some other tools that give you only a script
tag, it doesn't play nicely with something like Turbo that only replaces parts of the HTML rather than executing a full page refresh from the server.
You may find that it works on first visit, but if you navigate to a different page on your site and back again, the form is just a blank box. The problem is that the code doesn't unload when Turbo executes a page change, so when you go back to the page with the form on it, the code is still in memory and so it doesn't run again. Like I described with TinyMCE, you can use Stimulus to remove the code when the page unloads and replace it when it loads again.
The following assumes you have a Rails app with Stimulus installed, and that you have an account with Sender.net. You will also need to create an embedded form formatted to your liking.
Go to the forms section on Sender.net and click on the Overview button next to the form you wish to use. Scroll down the page, and you will see the javascript you need to add to your head section, which will look something like this:
<script>
(function (s, e, n, d, er) {
s['Sender'] = er;
s[er] = s[er] || function () {
(s[er].q = s[er].q || []).push(arguments)
}, s[er].l = 1 * new Date();
var a = e.createElement(n),
m = e.getElementsByTagName(n)[0];
a.async = 1;
a.src = d;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://cdn.sender.net/accounts_resources/universal.js', 'sender');
sender('xxxxxxxxxxxxxx')
</script>
You also need to add a div
to your page where you want the form to appear:
<div style="text-align: left" class="sender-form-field" data-sender-form-id="xxxxxxxxxxxxxxxxxxx"></div>
If you add the script to the head section of your application.html.erb
file, it will be loaded for every page on your site, whether the form is on it or not. This may be fine for you, but I only want it on my homepage, so I've placed it all in the same partial with the script
section wrapped in a content_for
block, so:
<%= content_for :head do %>
<script>
...
</script>
<% end %>
Just before the closing head
tag in application.html.erb, put:
<%= yield :head %>
This will automatically place the form on the page, but this is not what we want as, to solve the problem, we need to do this manually. To tell the script not to render the form on load, add ?explicit=true
to the end of the URL within the script tags, i.e. make the penultimate line read:
})(window, document, 'script', 'https://cdn.sender.net/accounts_resources/universal.js?explicit=true', 'sender');
The div
tag needs to be wrapped in another div
in order to add a Stimulus controller, like so:
<%= tag.div data: {controller: "sender-form", sender_form_signup_id_value: "xxxxxx"} do %>
<div style="text-align: left" class="sender-form-field" data-sender-form-id="xxxxxxxxxxxxxxxxxxx"></div>
<% end %>
Replace the "xxxxxx" for sender_form_signup_id_value
with the ID of the form at the top of the overview page for the form on Sender.net.
In app/javascript/controllers/
, create the file sender_form_controller.js
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static values = {
signupId: String
}
}
Sender.net provides some code to check whether their form code has yet loaded, which we can adapt for the connect()
method, calling the render
method to create the form:
connect() {
if (!window.senderFormsLoaded) {
window.addEventListener("onSenderFormsLoaded", () => {
this.render()
})
} else {
this.render()
}
}
render() {
window.senderForms.render(this.signupIdValue)
}
The disconnect()
method is quite straightforward, just calling the destroy()
method and passing the form id:
disconnect() {
window.senderForms.destroy(this.signupIdValue)
}
A final touch is to prevent these methods from running if this is a Turbo preview:
disconnect() {
if (!this.preview) window.senderForms.destroy(this.signupIdValue)
}
render() {
if (!this.preview) window.senderForms.render(this.signupIdValue)
}
get preview () {
return document.documentElement.hasAttribute('data-turbo-preview')
}
Add the controller to the app/javascript/controllers/index.js
file:
import SenderFormController from "./sender_form_controller.js"
application.register("sender-form", SenderFormController)
And that should be it. It doesn't render as quickly as an HTML form as it has to grab code from another server and render it, but at least you won't be left with an empty white box where there should be a form when you navigate away and back again.
Here is the Stimulus controller in full:
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static values = {
signupId: String
}
connect() {
if (!window.senderFormsLoaded) {
window.addEventListener("onSenderFormsLoaded", () => {
this.render()
})
} else {
this.render()
}
}
disconnect() {
if (!this.preview) window.senderForms.destroy(this.signupIdValue)
}
render() {
if (!this.preview) window.senderForms.render(this.signupIdValue)
}
get preview () {
return document.documentElement.hasAttribute('data-turbo-preview')
}
}
Top comments (0)