Stay (somewhat) aware of the Forms 2.0 event listener limit

If you ever see this error in your browser console —

— you've almost certainly done something wrong in JS.

But don't take it personally: that “something” might've been innocently copying-and-pasting code from the Marketo developer docs, where there's a non-zero amount of broken code. (Not bad-but-working code, which you come to expect with developer docs that are just getting a point across, but real bugs.)

Case in point: the example code for adding secondary submit buttons with the Forms 2.0 API.

The idea is that other buttons (or button-like elements) in a page can be used to submit, in addition to the built-in <button> at the base of every form.

So you can create stuff like this, if it suits your fancy:

The example uses this JS:

var btn = document.getElementById("secondary-submit-button");
btn.onclick = function() {
  // When the button is clicked, get the form object and submit it
  MktoForms2.whenReady(function (form) {
     form.submit();
  });
};

I'm not going after their use of onclick instead of a real event listener (file that under not-the-best-but-works).

Instead, let's narrate what this code is setting up:

Every time a certain button is clicked, a function will run for every current or future form in the page. That function will forcibly submit each corresponding form to Marketo, stopping only if form validation fails, and will run regardless of whether the form has any relationship to the original button.

That doesn't make much sense, does it?

You wanted something simple: a button should submit a single form, there and then.

You didn't want a button click to queue up a form submit for every single form, including ones that may still be lazily drawn into the page (think popups).

Imagine what happens if you have another form, unrelated to this double-button scenario, and that form doesn't have any mandatory fields (or the mandatory fields are filled via PreFill). Bang, off it goes to Marketo.

With the code above in place, every time the button is clicked — even if the form it appears to control can't submit due to validation errors — all the forms on the page, current and future, are queued to be submitted.

If you click the button 10 times and after that another form is added to the page, that form will submit 10 times in a row!

There can be some poor outcomes here, for sure.

And it actually gets a little bit worse. Here's where Event Emitter limits come into play. If you add more than 10 MktoForms2.whenReady event listeners you'll see this in the console:

This is flagging a possible browser memory leak, not necessarily a real-world leak, to be fair. But you want nothing to do with this error either way. As is well-known in the Joyent/NodeJS world (where the Event Emitter module comes from) you shouldn't try to escape this error by upping the limit, but by fixing your code to be smart about memory.

Here's how the special submit button function should actually be written. Note the reversed order, from whenReady to onclick:

// only call whenReady once
MktoForms2.whenReady(function(form){
  // only attach the click event if it's an interesting form ID
  if (form.getId() == 1234) {
    var btn = document.getElementById("secondary-submit-button");
    btn.onclick = function() {
      // When the button is clicked, get the form object and submit it
      form.submit();
    };
  }
});

Presto! Now you're only adding a single whenReady event listener. As usual, the whenReady fires once for all forms on the page. But it only cares about the one interesting form ID 1234. When it sees that ID, it wires up the alternate submit button. Otherwise, it exits harmlessly.

You now have a correct setup. In English-ish:

When a form first loads into the page, a function checks if the form is in a list of special forms. If the form is in the list, an onclick function is added to the corresponding submit button. The onclick function submits the form, barring validation errors.

To protect even more against surprises you should have each secondary submit button (since there could be more than one, why not?) use a class like class="secondary-submit" as opposed to a single-use id, and an attribute like data-submit-form="1234" to store the corresponding form ID. I leave these exercises to you.