Accurately background-loading the MktoForms2 global object

SanfordWhiteman
Level 10 - Community Moderator
Level 10 - Community Moderator

To use the Forms 2.0 JS API – including simply loading a form — you call methods on the global object window.MktoForms2. MktoForms2.loadForm loads a form,  MktoForms2.whenReady and MktoForms2.whenRendered add custom behaviors, and so on.

More to the point: MktoForms2 is Forms 2.0.

Sticking with the default embed code — not putting it into a tag manager or anything elaborate, just pasting it into the original HTML source of the page — you always know when MktoForms2 is ready for use. MktoForms2 is created by the remote file forms2.min.js. And forms2.min.js, like all remote <script> tags without special attributes, loads in synchronous/blocking mode:

<script src="https://pages.example.com/js/forms2/js/forms2.min.js"></script>
<form id="mktoForm_787"></form>
<script>MktoForms2.loadForm("//pages.example.com", "123-XOR-456", 787);</script>

In the default embed code shown above, the call to MktoForms2.loadForm in the second, local <script> block is guaranteed to work, because it comes after the first, remote <script> that loads forms2.min.js. The browser ensures the first script has loaded and run before proceeding.

 

The standard embed is no problem

Since you know MktoForms2 is available, you can add special form behaviors in another local or remote script right after the embed code (or anywhere after, though it’s cleaner to keep them together):

<script src="https://pages.example.com/js/forms2/js/forms2.min.js"></script>
<form id="mktoForm_787"></form>
<script>MktoForms2.loadForm("//pages.example.com", "123-XOR-456", 787);</script>
<script>
  MktoForms2.whenReady(function(mktoForm){
    // some super-cool extended form behaviors from Sandy’s blog or elsewhere
  });
<script>

On a Marketo LP with a named form, you also know when MktoForms2 will be ready for use. On an LP, Marketo places the remote <script src="forms2.min.js"> wherever in the body the form appears, followed by the form descriptor and form loading code. You can safely put your custom form behaviors just before the closing </body> tag, since you know that somewhere above there, the MktoForms2 object was created.

 

Problem time

But things change drastically when your web team departs from the standard embed code. They might try to switch forms2.min.js to defer or async mode, or load it via a tag manager, or inject it via a package/dependency manager, or a combo of those tactics.

That breaks stuff.

Just the addition of defer (look closely at the first <script>) breaks the embed code:

<script src="https://pages.example.com/js/forms2/js/forms2.min.js" defer></script>
<form id="mktoForm_787"></form>
<script>MktoForms2.loadForm("//pages.example.com", "123-XOR-456", 787);</script>

Now, MktoForms2.loadForm most certainly will not work, because MktoForms2 does not exist yet.

2021-02-11-17_31_33-CodePen---MktoForms2-__-_script-defer_-and-capturing-listener---Mozilla-Firefox

 

Why you might defer

Now let’s think about why you might add the  defer attribute, since it’s not a bad idea... as long as you know how to deal with the complications.

In the event that forms2.min.js takes a long time to load, and the embed code isn’t very low down in the document <body>, then you’re blocking other assets from even starting to load.

Say your CMS outputs the whole embed code in the same place as the <form> tag. (This isn’t necessary — the initially empty <form id="mktoForm_nnn"> can be output on its own, separate from the scripts — but it’s not weird for a CMS to spit out a “block” in one place in the DOM.)

Here’s an expanded snippet showing that config:

<div>
    Some body content here.
</div>
<script src="https://pages.example.com/js/forms2/js/forms2.min.js"></script>
<form id="mktoForm_787"></form>
<script>MktoForms2.loadForm("//pages.example.com", "123-XOR-456", 787);</script>
<script>
  /* some JS to animate the page, send analytics hits, whatever */
</script>
<style>
  /* some CSS to style up the page */
</style>

That snippet is totally fine! But the way it works, under the hood, may be surprising.

You may not realize that by default, browsers play dumb about dependencies. They don’t apply special intelligence to determine which scripts need to run before other scripts. They don’t figure out on their own whether styles should be applied even if scripts above them haven’t run — or the reverse, whether scripts can run if stylesheets above them haven’t loaded.

Rather, browsers assume the order of elements in the DOM represents a chain of dependencies.

So if you have <script> tag A, followed by <script> tag B, followed by <style> tag C, the browser assumes the code in A needs to run before the code in B, and the code in B needs to run before the styles in C are applied.

There’s absolutely nothing wrong with this being the default interpretation. You, the page author, are responsible for the DOM order, and the browser doesn’t second-guess you. And, putting performance aside for the moment, you can never go wrong by assuming each element requires the element above it to be complete. The page will always work that way.

So let’s visualize the implicit dependencies in the snippet above:

Blog-Post-on-MktoForms2_00-1

Should make sense, right? Each element waits for the element above it. And that even includes the <form> tag! The form is an empty local element, so there’s no remote resource to wait for, and it’ll render instantaneously (unless you’re using a prototype browser from 1991 or something). Nevertheless, it’s technically true that the second <script> waits for the <form> to be done before it loads and runs, even if that means a 1-microsecond pause.

Now suppose forms2.min.js takes an abnormally long amount of time to load due to network conditions — 2 seconds as opposed to its usual < 125ms.

In this case, the (empty)  <form> after it will wait those 2s to render, and in turn the <script> that loads the actual form will take ~2s to start the form injection process. And all the while the <script> and <style> that come after that, which might have nothing to do with forms at all, are also delayed by the corresponding amount.

To be clear, MktoForms2.loadForm needing MktoForms2 to be created by forms2.min.js is not a problem — that relationship is always there!

It’s the non-form-related collateral damage that’s a problem. Assets lower in the DOM are blocked, though they could potentially run while the browser is waiting for forms2.min.js in the background.

Before continuing, let me be clear that “blocking” sounds worse in theory than it is in practice. 99.9% of the time, it’s just a technical nuance, and it’s still a sensible default. MktoForms2 is typically available in under 125ms on a low-latency network, and if the form is the most important thing on an LP, waiting for the form in the background won't help — the end user is waiting, no matter what.

Of course, this post wouldn't exist if I didn't share the concern that loading the forms library in the foreground may have tangible (and avoidable) user-facing effects. But keep it in perspective: very few pages actually suffer by depending on the default embed code.

 

An attempted solution (that’s worse than the problem)

A web developer (who’s still kind of new to this) might try to add the defer attribute to the <script> that loads forms2.min.js:

 

<div>
    Some body content here.
</div>
<script src="https://pages.example.com/js/forms2/js/forms2.min.js defer"></script>
<form id="mktoForm_787"></form>
<script>MktoForms2.loadForm("//pages.example.com", "123-XOR-456", 787);</script>
<script>
  /* some JS to animate the page, send analytics hits, whatever */
</script>
<style>
  /* some CSS to style up the page */
</style>

This avoids one problem case — but introduces a whole new problem that’s even worse.

The defer attribute means the browser will not stop the parsing process while loading the <script>. Instead, it’ll load and queue up the script in the background and will only execute the code after the whole (initial) document is parsed.

That sounds good at first, because if the script is queued in the background, a delay in loading forms2.min.js can’t affect scripts, styles and anything else that come after it in the DOM.

Here’s a visualization of the new dependencies with defer:

Blog-Post-on-MktoForms2_01-1

Now, the first <script> load is moved to the background. No matter how long form2.min.js takes, it won’t interfere with the empty <form> rendering.

The subsequent <script> still technically waits on the <form>. But because the <form>, in practice, is instantly done, that means  MktoForms2.loadForm attempts to run immediately after forms2.min.js is queued to load — but, by definition, before it finishes loading and executing.

So MktoForms2.loadForm fails. As it well should, since MktoForms2 doesn’t exist:

2021-02-11-17_31_33-CodePen---MktoForms2-__-_script-defer_-and-capturing-listener---Mozilla-Firefox

 

You can’t defer a inline script

Your next thought might be, “Just add  defer to the next script, so MktoForms2.loadForm will wait as well.”

Nope.

There’s no such thing as an inline script that’s defer-ed. If you add defer, like this...

<script defer>
    alert("I’m running now!");
</script>

... all you’ve done is created invalid HTML. The defer attribute is ignored, and the code still runs immediately. So MktoForms2.loadForm will fail in exactly the same way.

 

What we’re looking for

The defer example shows how easy it is to break stuff — one li’l attribute for resilience against network/server slowdowns and nothing works anymore!

The problems get worse with more complex forms of background loading, like the  async attribute, or dynamic script injection with new Script(), or any number of package managers and tag injectors and whatnot.  

They all have the same underlying problem: when scripts load independently and asynchronously, then script A cannot depend on script B. (Kind of a tautology as “independent” and “dependency” are contradictory!)

So we must (re)gain control over execution order.

What we’re looking for is a way to run code that depends on MktoForms2 immediately upon MktoForms2 becoming available — not some time later, but immediately. (Its drawbacks aside, the default blocking behavior guarantees script B runs immediately after script A, not one moment before, not some time after.)

To be explicit about what we’re NOT looking for:

  • Timeouts, intervals, and polling: that kind of primitive guesswork is, IMNSHO, unacceptable in a professional context.
  • Listening for well-known events like window onload and document DOMContentLoaded: these are both irrelevant to asynchronous scripts, which can start or finish before or after parsing is complete.
  • Adding a load listener to the forms2.min.js script itself: loading the main Forms 2.0 library might be a separate task from adding custom form behaviors, potentially managed by another team. So we won’t necessarily know how or where the library is loaded; our code just needs to fire when.

 

The solution

Turns out there’s a uniquely awesome way of capturing the exact moment forms2.min.js has loaded, regardless of how it was loaded!

That way is a capturing event listener that listens for document load events.

Not window or body load, not DOMContentLoaded (a.k.a. jQuery’s ready), and not a typical bubble-stage listener — but specifically a capturing-stage listener on document load.

I’ve droned on long enough already, so it’s up to you to learn about bubbling and capturing (one of many great guides is here). Suffice it to say that every loaded remote <script> fires a load event during the capture stage, immediately after the script has executed.

Each load event contains the URL (src) of the <script> that fired it, but we actually don’t care about that. All we care about is whether MktoForms2 exists when the event fires.

  • If it doesn’t exist, we keep listening as more events come in.
  • Once it does exist, we stop listening for further load events and kick off our form-related goodness.

At its simplest:

function doOrQueueFormsStuff() {
  if (typeof MktoForms2 != "object") {
    document.addEventListener("load", doOrQueueFormsStuff, true);
  } else if (!doOrQueueFormsStuff.done) {
    document.removeEventListener("load", doOrQueueFormsStuff, true);
    doOrQueueFormsStuff.done = true;
    MktoForms2.loadForm("//pages.example.com", "AAA-BBB-CCC", 787);
  }
}
doOrQueueFormsStuff();

Adding a wrapper function to protect scope and with a few more comments:

(function() {
    function doOrQueueFormsStuff() {
      if (typeof MktoForms2 != "object") {
        document.addEventListener("load", doOrQueueFormsStuff, true);
      } else if (!doOrQueueFormsStuff.done) {
        document.removeEventListener("load", doOrQueueFormsStuff, true);
        doOrQueueFormsStuff.done = true;

        /* 
         now you can load form(s), add custom behaviors, 
         and inject other scripts that require MktoForms2
        */
            
        MktoForms2.loadForm("//pages.example.com", "AAA-BBB-CCC", 787);
        // MktoForms2.whenReady(...)            
        // MktoForms2.whenRendered(...)
        // var someOtherScript = new Script();
        // script.src="https://www.example.com/myBehaviors.js";
      }
    }
    doOrQueueFormsStuff();      
})();

 

Running doOrQueueFormsStuff will either do your stuff immediately (if MktoForms2 already exists) or queue your stuff for later (if MktoForms2 isn’t there yet).

Here’s an example of an embed code safely souped up with defer:

<form id="mktoForm_787"></form>
<script src="https://pages.example.com/js/forms2/js/forms2.min.js" defer></script>
<script>
(function(){
  function doOrQueueFormsStuff() {
    if (typeof MktoForms2 != "object") {
        document.addEventListener("load", doOrQueueFormsStuff, true);
    } else if (!doOrQueueFormsStuff.done) {
        document.removeEventListener("load", doOrQueueFormsStuff, true);
        doOrQueueFormsStuff.done = true;
        MktoForms2.loadForm("//pages.example.com", "AAA-BBB-CCC", 787);
    }
  }
  doOrQueueFormsStuff();
})();
</script>

 

To test, I artificially slowed down forms2.min.js so it takes 3 seconds to download (obviously an extraordinarily rare event, but you have to exaggerate in order to get a useful test). Recorded a couple of GIFs so you can compare.

Here’s the standard embed code with a 3-second delay:

Video_2021-02-16_002913

 

And here’s the embed code with defer and doOrQueueFormsStuff:

Video_2021-02-16_003706

 

With the standard embed, “Content below form” doesn’t show until forms2.min.js has loaded. But with the custom embed, “Content below form” shows immediately, as forms2.min.js is allowed to load in the background. In both cases, the form itself displays as soon as it’s able.

Enjoy!

9025
13
13 Comments