Patching a bug with input masks + multiple Marketo forms on the same page

If I didn’t realize this bug existed until today, safe to say nobody else did!

The input mask feature for Text fields adds client-side validation:

There are s a couple of posts from way back about tweaking mask logic and switching mask patterns based on other fields (like having the Phone mask based on Country). But when you combine input masks with multiple forms, something buggy happens.

As noted in my previous post on the requirement that all forms load from the same domain (i.e. your LP domain), the Forms 2.0 library is smart about only doing certain things once.

It only loads polyfills (most notably these days, the date picker UI for <input type="date"> on IE) a single time at most, and only if the browser doesn’t natively support the feature.

It’s kind of a misnomer,* but the input mask plugin is also managed by the polyfill logic. Unfortunately, the check to see if the polyfill is necessary is only done for the first form loaded.

So if the first form called by loadForm() has masked fields, the plugin is loaded and works for all subsequent forms. But if the first form doesn’t have any masked fields, the Forms 2.0 library doesn’t load the plugin and marks the check as done. It doesn’t re-check.

Imagine form ID 787 has a masked Phone along with a bunch of other fields, but form ID 1221 just has First, Last, and Email. The outcome will be different if 787 is loaded first:

<script>
    MktoForms2.loadForm("//pages.example.com", "123-XOR-456", 787);
</script>

<div>Other stuff</div>

<script>
    MktoForms2.loadForm("//pages.example.com", "123-XOR-456", 1441);
</script>

vs. the other way around:

<script>
    MktoForms2.loadForm("//pages.example.com", "123-XOR-456", 1441);
</script>

<div>Other stuff</div>

<script>
    MktoForms2.loadForm("//pages.example.com", "123-XOR-456", 787);
</script>

In the second case, Phone will end up being a freeform Text field with no mask (it’ll still work, but the person can enter anything).

Remember, the order of loadForm() calls need not relate to visual order in the page. The “first” form could be displayed in the footer, while the “second” form could be the main focus of the page. You’re not obligated to obey a specific DOM order for your <form> tags (especially not in these days of Flexbox, wide support for position: fixed, and dynamically generated HTML).

The fix

To make sure input masks work regardless of form order, we simply re-run the same logic the Forms 2.0 library uses internally, but without any other conditions:

   (function() {
      function isPlatform(platform) {
         if (navigator.userAgentData && navigator.userAgentData.platform) {
            return navigator.userAgentData.platform === platform;
         } else {
            return new RegExp(platform).test(navigator.userAgent);
         }
      }
      
      let forms2BaseURL = "//pages.example.com/js/forms2/";
      MktoForms2.Modernizr.load({
         test: isPlatform("Android"),
         yep: [forms2BaseURL + "polyfills/inputmask/jquery.mask.min.js"],
         nope: [forms2BaseURL + "polyfills/inputmask/inputmask.min.js"]
      });
   })();

Yes, the polyfill is different on Android than other platforms. I’m not even gonna touch that one today.☺

Notes

* There’s no browser that can natively do what the input mask does, so it’s not a “polyfill” for anything. Yes, you can do regexp pattern matching with <input pattern="\d{3}-\d{3}-\d{4}"> but that won’t do the underscore-cursor-placeholder stuff.