DEV Community

Cover image for Use Stimulus for Sender.net signup form in Rails app
djchadderton
djchadderton

Posted on

Use Stimulus for Sender.net signup form in Rails app

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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 %>
Enter fullscreen mode Exit fullscreen mode

Just before the closing head tag in application.html.erb, put:

<%= yield :head %>
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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 %>
Enter fullscreen mode Exit fullscreen mode

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
  }

}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

The disconnect() method is quite straightforward, just calling the destroy() method and passing the form id:

disconnect() {
  window.senderForms.destroy(this.signupIdValue)
}
Enter fullscreen mode Exit fullscreen mode

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')
}
Enter fullscreen mode Exit fullscreen mode

Add the controller to the app/javascript/controllers/index.js file:

import SenderFormController from "./sender_form_controller.js"
application.register("sender-form", SenderFormController)
Enter fullscreen mode Exit fullscreen mode

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')
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)