Using Lity (and similar lightbox plugins) with Marketo form embeds

There’s a post in my drafts explaining that you don’t need any custom lightbox code in modern browsers — neither MktoForms2.lightbox() nor a 3ʳᵈ-party library.

In the meantime, though, duty calls. A Nation user had already decided to use the Lity lightbox library but couldn’t figure out why their form — or more precisely, the form’s children — rendered twice inside the lightbox:

Below is the code they were using. Can you spot the error?

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="https://offers.example.com/rs/123-NBB-456/images/lity2.js"></script>
<link rel="stylesheet" href="https://offers.example.com/rs/123-NBB-456/images/lity.css">
<div id="inline" style="overflow:auto;padding:20px;width:600px" class="lity-hide">
   <script src="//offers.example.com/js/forms2/js/forms2.min.js"></script>
   <form id="mktoForm_5050"></form>
   <script>
      MktoForms2.loadForm("//offers.example.com", "123-NBB-456", 5050);
   </script>
</div>
<a href="#inline" class="btn dream-btn" data-lity>Let's Talk</a>

If you haven’t figured it out, it’s that the lightboxable container contains not only the <form> tag but the <script> tags too.

When it’s time to show the popup, Lity reinjects all the child elements of the container — the <div> with class lity-hide — inside a new container.[1]

In other words, it temporarily ejects (detaches) the elements from the DOM and then immediately injects (reattaches) them inside the modal. In theory, this would be harmless. Except the elements include scripts. And that means the scripts run again![2]

Therefore, the form inputs, labels, etc. render a 2nd time inside the parent <form>. Same thing that always happens when you run MktoForms2.loadForm() twice for the same form ID.

Deduping the form elements

Keep scripts outside of the lightbox:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="https://offers.example.com/rs/123-NBB-456/images/lity2.js"></script>
<link rel="stylesheet" href="https://offers.example.com/rs/123-NBB-456/images/lity.css">
<div id="inline" style="overflow:auto;padding:20px;width:600px;" class="lity-hide">
   <form id="mktoForm_5050"></form>
</div>
<script src="//offers.example.com/js/forms2/js/forms2.min.js"></script>
<script>
   MktoForms2.loadForm("//offers.example.com", "123-NBB-456", 5050);
</script>
<a href="#inline" class="btn dream-btn" data-lity>Let's Talk</a>

Presto! Now the form elements render only once.

Even better

We still aren’t getting optimal performance. As I mentioned in my recent post on Chosen, if you’re enhancing Forms 2.0 forms using jQuery plugins, you don’t need to separately load jQuery. There’s already a copy of jQuery inside forms2.min.js. So let’s use the built-in copy:

<!-- load Forms 2.0 library first, so we can use its builtin jQuery -->
<script src="//offers.example.com/js/forms2/js/forms2.min.js"></script>
<script>
   if(window.jQuery){
      window.jQueryRestore = window.jQuery;
   }
   window.jQuery = MktoForms2.$;
</script>
<script src="https://offers.example.com/rs/123-NBB-456/images/lity2.js"></script>
<script>
   if(window.jQueryRestore) {
      window.jQuery = window.jQueryRestore;
      delete window.jQueryRestore;      
   } else {
      delete window.jQuery;
   }
</script>
<link rel="stylesheet" href="https://offers.example.com/rs/123-NBB-456/images/lity.css">
<div id="inline" style="overflow:auto;padding:20px;width:600px;" class="lity-hide">
   <form id="mktoForm_5050"></form>
</div>
<script>
   MktoForms2.loadForm("//offers.example.com", "123-NBB-456", 5050);
</script>
<a href="#inline" class="btn dream-btn" data-lity>Let's Talk</a>

Even if your site uses jQuery elsewhere, it’s best to use the method above for forms plugins. Troubleshooting is harder if Marketo’s jQuery and another jQuery each handle a subset of form behaviors.

Notes

[1] This is the way most lightbox libraries work. I prefer to put content inside modal-capable containers to begin with, but that means you have to build HTML with modals in mind instead of adding the effect later.

[2] To developers’ continuous surprise, dynamically injecting a <script> element may or may not run the code. Depends on whether you use innerHTML (won’t run) or the more methodical DOM methods (will run). And either way, a given <script> element should be expected to run a maximum of one time.

This means there’s no Right Way™ to add HTML. innerHTML is faster if you have a big block of prefab HTML, while DOM is faster for lots of smaller elements. But you don’t always know in advance if a big block includes scripts. Some JS frameworks support a hybrid approach, injecting a block using innerHTML, then scanning the result for <script> elements and reinjecting new scripts only for those elements so they run as expected!