If you absolutely must load embeds from different domains

SanfordWhiteman
Level 10 - Community Moderator
Level 10 - Community Moderator

In a recent post, you learned that having multiple form embeds in the same page, with loadForm called from more than one domain, causes fatal errors.

 

Those errors can break any form on the page, depending on load order. (I didn’t mention this explicitly before, but when multiple forms are injected via GTM or another async mechanism, you can’t predict the order — meaning you can’t even predict which form(s) will fail on a given pageview.)

 

The solution is to load all forms from the same domain, preferably your LP domain.[1]

 

But there’s one case when you can’t do that: when you’re deliberately loading embeds from different Marketo instances. Not just different aliases for the same instance, but totally different instances, like one for B2B and one for B2C.

 

In this case, you can’t simply standardize on one domain. But what you can do is force the Forms 2.0 loader to create a separate instance of MktoForms2 for each domain. This will in turn create a separate IFRAME for each instance of the library, so they can submit side-by-side.

 

Running MktoForms2 SxS

This task is superficially simple: you’d think you could delete the global MktoForms2 before each embed, so the library will think it hasn’t been loaded yet. But that simple method doesn’t work in practice, due to a pesky problem with polyfills (such as the Input Mask and Date plugins).

 

So I ended up having to write this just-short-of-too-crazy Forms 2.0 Instance Manager. Pretty proud of it, gotta admit... good to flex the ol’ Getter and Setter muscles.

 

First, include the core function:

/**
  * Enable side-by-side Marketo Forms 2.0 libraries from different domains
  * @author Sanford Whiteman
  * @version v1.0 2021-12-30
  * @copyright © 2021 Sanford Whiteman
  * @license Hippocratic 3.0: This license must appear with all reproductions of this software.
  *
  */   
function MktoForms2PerInstanceManager(instanceBaseURLs,iter){

    let instances = new Map();

    /* evergreen browsers can use an unthrown Error, IE 11 needs throw */
    function genStack(){
        let tempError = new Error(),
            stack = tempError.stack;

        if (stack) {
            return stack;
        } else {
            try {
                throw tempError;
            } catch (error) {
                return error.stack;
            }
        }
    }

    /* no one said finding the source URL wouldn't be sloppy, but it works */
    function getInstanceURLFromStack(stack){      
        return instanceBaseURLs.filter(function(url){
            return stack.indexOf(url) != -1;
        })[0];
    }

    Object.defineProperty(window, "MktoForms2", {
        configurable: true,
        get: function() {
            let stack = genStack(),
                instanceKey = getInstanceURLFromStack(stack);

            return instances.get(instanceKey);
        },
        set: function(library) {
            let stack = genStack(),
                instanceKey = getInstanceURLFromStack(stack);

            instances.set(instanceKey, library)         
        }
    })

    return instances;

}
Instance Manager core code

 

Then create an Instance Manager object, passing it your different instances’ URLs:

/* add all possible base URLs of your different instances */
let instanceURLs = [ "//pages.example.com", "//explore.example.net" ];

/* create the instance manager */
let MktoForms2Instances = new MktoForms2PerInstanceManager(instanceURLs);
Creating a Manager object

 

To load forms, add events, and so on, first get() the appropriate instance from the Manager. The methods are otherwise the same stuff from the Forms 2.0 API:

<form id="mktoForm_1449"></form>
<script src="//pages.example.com/js/forms2/js/forms2.js"></script>
<script>
/* use an instance */
MktoForms2Instances.get("//pages.example.com").loadForm("//pages.example.com", "410-XOR-673", 1449);
</script>

<form id="mktoForm_1379"></form>
<form id="mktoForm_787"></form>
<script src="//explore.example.net/js/forms2/js/forms2.js"></script>
<script>
/* use another instance */
MktoForms2Instances.get("//explore.example.net").loadForm("//explore.example.net", "410-XOR-673", 1379);
MktoForms2Instances.get("//explore.example.net").loadForm("//explore.example.net", "410-XOR-673", 787);
</script>
Loading 3 forms from 2 side-by-side instances

 

You can add events to all forms on all instances by forEach()-ing over the instances:

MktoForms2Instances.forEach(function(MktoForms2Library) {      
    MktoForms2Library.whenReady(function(mktoForm) {
        mktoForm.onSubmit(function(mktoForm) {
            console.log("Marketo form " + mktoForm.getId() + " on the move")
        });
    });
});
Using forEach to get every instance automatically

 

Or target a specific instance + form only if it’s present:

if (MktoForms2Instances.has("//explore.example.net")) {      
    MktoForms2Instances.get("//explore.example.net").whenReady(function(mktoForm) {         
        if(mktoForm.getId() == 787) {
            mktoForm.addHiddenFields({
                Email: "teknkl+test0001@email.invalid",
                Letter_Date__c: "2022-02-01"
            });
        }
    });
}
Finding an instance and targeting one form ID
 
NOTES

[1] You could load them all from the app-* domain, but then they’ll be blocked when Tracking Protection is on. So kill two birds with one stone and standardize on your LP domain.

888
0