What’s the difference between a single-form loadForm() callback and the all-forms whenReady()?
Let’s say you want to immediately switch to lightbox mode (using the Forms 2.0 native lightbox()
feature) when a specific form is ready.
You’d think there’d be zero difference between Approach #1:
MktoForms2.whenReady(function(mktoForm){
if(mktoForm.getId() == 1234)
MktoForms2.lightbox(mktoForm).show();
}
});
MktoForms2.loadForm("//pages.example.com", "410-ABC-673", 1234);
And Approach #2:
MktoForms2.loadForm("//pages.example.com", "410-ABC-673", 1234, function(mktoForm){
MktoForms2.lightbox(mktoForm).show();
});
Both snippets load the form and fire a callback when it’s ready.
In #1, the callback function technically runs when any form becomes ready, but the lightbox is conditioned on the form ID. (For a page with only one form, that condition wouldn’t be necessary.)
In #2, the callback runs only when form 1234 is ready, since it’s part of that form’s loadForm()
call.
All other things being equal, #1 would be better, since you can manage browser behaviors (like lightbox()
-ing) separately from loading the form from the server.
But they’re not equal! Check out the momentary flicker of the form at the upper left in Approach #1:
No such flicker for Approach #2, where the form only shows in lightbox mode:
Note the behavior of #1 is the same whether whenReady()
comes before or after loadForm()
. I just put it before so you wouldn’t be tempted to think that was the cure.
Why does this happen?
It’s a subtle implementation difference that has to do with when a browser repaints.
The way an onSingleFormReady
callback works — it doesn’t have that official name, it’s really just “the 4th argument to loadForm
” — is that immediately after the form is rendered, the callback runs. “Immediately” meaning on the very next line, in the same execution context:
form.render();
callback();
In contrast, the whenReady
callback might be also be thought of as “immediate,” but it isn’t, let’s say, as immediate. Instead, whenReady
listeners fire asynchronously. There are actually 2 different async modes[1], but they’re both equivalent (for today’s purposes) to:
form.render()
setTimeout(callback, 0);
setTimeout
with a delay of 0, as you may already know, doesn’t really mean a delay of zero milliseconds. It means 4ms + whatever additional time the browser needs to schedule a new task. So it might be just 4ms or a few millis more, maybe even a few seconds if other tasks are going crazy in the browser at that moment.
But even if it takes the absolute minimum of 4ms, the browser still schedules a new task, “turning” the JavaScript event loop. And since the initial task has completed (the one that ends with the call to setTimeout
) the browser takes the opportunity to repaint the window.
So that’s why you see the flicker of the form in its original position before the ever-so-slightly-delayed function runs and moves the <form>
into the lightbox.[2]
You might not ever notice this difference, of course. 99.999% of form behaviors (which you should be adding using whenReady
, by the way!) don’t need to repaint the screen. The lightbox is a notable exception because the <form>
element is relocated in the DOM.
Solving the flicker even with whenReady
Luckily, you can avoid the flicker in all cases with a little CSS. When creating the shell <form>
element, add a special attribute:
<form data-mktomodal="true" id="mktoForm_1234"></form>
Then hide forms with that attribute by default, only showing them after they’re moved inside the modal container (i.e. lightbox):
.mktoForm[data-mktomodal="true"] {
visibility: hidden;
position: absolute;
}
.mktoModal .mktoForm[data-mktomodal="true"] {
visibility: visible;
position: static;
}
Why visibility
and position
instead of display
? Try the alternative and see. :)
Notes
[1] One is a literal setTimeout
. The other is an EventEmitter event, which is also asynchronous.
[2] You can prove this easily by using the onSingleFormReady
but forcing a setTimeout
inside it:
MktoForms2.loadForm("//pages.example.com", "410-ABC-673", 1234, function(mktoForm){
setTimeout(function(){
MktoForms2.lightbox(mktoForm).show();
},0);
});
That’ll demonstrate the flicker due to repaint.