In programming slang, a footgun is a prominent feature thatβs so easy to misuse β to shoot yourself in the foot β that everyone should be wary of it, even if it has its uses.
Perhaps a bit strong to describe the form.submittable(true)
method of the Forms 2.0 JS API. Yet that method is used incorrectly the majority of the time (including by yours truly!) so letβs see how to use it right.
You typically see form.submittable(true)
used like so:
MktoForms2.whenReady(function(readyForm){
readyForm.onValidate(function(isNativeValid){
if(!isNativeValid) return;
const currentValues = readyForm.getValues();
readyForm.submittable(false);
if( currentValues.FirstName == "Santa" ) {
readyForm.submittable(true);
} else {
readyForm.showErrorMessage("Must Be Santa");
}
});
});
Thatβs a basic custom validation function:
if(!isNativeValid)
makes sure the native validation rules succeeded (i.e. Form Editor rules like fields marked Required and string/number patterns)- if not, exit because you want those rules to pass before running custom stuff
- if so, continue
- store the current field values:
form.getValues()
- set the form to not be submittable:
form.submittable(false)
- check a custom validation rule
- if validation succeeds, set the form back to submittable with
form.submittable(true)
- if validation fails, show an error message
- if validation succeeds, set the form back to submittable with
Looks right, right? Indeed, if thatβs the only custom onValidate
function, anywhere on your page or in external scripts, youβre fine. But in reality, extended validation gets added in different places. Thereβs a remote <script src>
that does email address validation, or you add one <script>
in GTM and another <script>
right on the LP, etc.
To be clear: separating validation tasks into different functions like this is best practice. But you have to write each function so it knows it might be in a chain of validation functions. People tend to not do that.
Look at the following onValidate
chain, which adds another custom validation rule, making sure a Checkboxes field has at least 2 boxes checked:
<script>
MktoForms2.whenReady(function(readyForm){
readyForm.onValidate(function(isNativeValid){
if(!isNativeValid) return;
const currentValues = readyForm.getValues();
readyForm.submittable(false);
if( currentValues.FirstName == "Santa" ) {
readyForm.submittable(true);
} else {
readyForm.showErrorMessage("Must Be Santa");
}
});
});
</script>
<script>
MktoForms2.whenReady(function(readyForm){
readyForm.onValidate(function(isNativeValid){
if(!isNativeValid) return;
const currentValues = readyForm.getValues();
readyForm.submittable(false);
if( [].concat(currentValues.MeetingAvailability).length >= 2 ) {
readyForm.submittable(true);
} else {
readyForm.showErrorMessage("Must select at least 2 meeting times")
}
});
});
</script>
(Iβve shown the 2 <script>
tags right next to each other for simplicity, but in reality expect them to be in entirely different places.)
Can you spot the bug? Take a minute to think through possible outcomes. Iβll wait.
π΄
π΄
π΄
OK. The bug is that if the personβs First Name isnβt βSantaβ but they did check 2 meeting times, the form will submit. While if the personβs First Name is βSantaβ and they didnβt check 2 meeting times, the form wonβt submit. Thatβs not the way itβs supposed to work: the validation rules are supposed to both succeed!
The cause of the bug is assuming that when each onValidate
listener runs, form.submittable()
starts out as true
, so you restore it to true
. Thatβs a bad assumption! It may start out as false
, in which case you want to leave it that way β and donβt even bother running other validation logic.[1]
At the same time, each new pass through the onValidate
chain (e.g. after the person changes form values and re-submits) must be independent of the last pass, which might have left submittable
set to false
.
So you need two complementary measures:
(1) Add a first listener that that only sets form.submittable(true)
, nothing else.
(2) In all subsequent listeners, check if form.submittable()
is still true
at the top of the function.
For (1), you might wonder how to ensure youβre adding the very first listener. Truth is, thereβs no one-size-fits-all way β depends on your overall setup[2] β but putting it directly after the form embed[3] suffices in most cases:
<script src="//pages.example.com/js/forms2/js/forms2.min.js"></script>
<form id="mktoForm_123">
<script>MktoForms2.loadForm("pages.example.com", "123-ABC-456", 9999);</script>
<script>
MktoForms2.whenReady(function(readyForm){
readyForm.onValidate(function(isNativeValid){
if(!isNativeValid) return;
// this is the known-first listener, so set to `true`
readyForm.submittable(true);
});
});
</script>
For (2), check submittability alongside native validity:
<script>
MktoForms2.whenReady(function(readyForm){
readyForm.onValidate(function(isNativeValid){
if( !isNativeValid || !readyForm.submittable() ) return;
const currentValues = readyForm.getValues();
readyForm.submittable(false);
if( currentValues.FirstName == "Santa" ) {
readyForm.submittable(true);
} else {
readyForm.showErrorMessage("Must Be Santa");
}
});
});
</script>
<script>
MktoForms2.whenReady(function(readyForm){
readyForm.onValidate(function(isNativeValid){
if( !isNativeValid || !readyForm.submittable() ) return;
const currentValues = readyForm.getValues();
readyForm.submittable(false);
if( [].concat(currentValues.MeetingAvailability).length >= 2 ) {
readyForm.submittable(true);
} else {
readyForm.showErrorMessage("Must select at least 2 meeting times")
}
});
});
</script>
Thatβs it!
Notes
[1] Some form designs require checking all validation rules, regardless of whether form.submittable()
was already false
. That lets you add a red border around all invalid fields at once, for example. But you still must make sure you donβt set form.submittable(true)
unless every rule has passed. (The code for always-check-all-rules is more complex but you can ask about it in the comments.)
[2] Complex load sequences, like using my onMktoForms2Ready pattern, make adding a known-first listener pretty tricky.
[3] On a Marketo Guided LP Template, this means directly after the <div class="mktoForm">
element (the embed code is automatically injected inside that <div>
).