No matter how tempting, never do this with Marketo string fields:
let jsVar = "{{lead.Some Field}}";
Why? Because {{lead.tokens}}
are HTML-encoded, not JS-encoded. That is, they’re only supported in text content, like so:
<div>{{lead.Some Field}}</div>
Failing to account for this will lead to broken pages — and you won’t find the bugs during testing unless you create a wide enough range of test values.
The solution, as I previewed in the recent post on form field visibility, is to output tokens into HTML elements and read them using JS. Simple code for this is below. But first, a little encoding review.
What is HTML encoding?
Some characters have special meaning in HTML markup. Most important are the ubiquitous <
and >
(used to denote HTML tags) and "
and '
(used to quote HTML attributes, as in <div class="whatever">
).
When these characters should be treated as mere text (i.e. as non-special) they must be encoded. Otherwise, you’ll get broken HTML. And even worse, you can be vulnerable to XSS attacks.
You’ve surely seen HTML encoding before: <
for <
, >
for >
, and
for a non-breaking space. In fact, any character can be HTML-encoded using its numeric codepoint, but for readability it’s best to encode only those that can’t be used literally.[1] For example, take this raw unencoded string:
My name is "Joe"
Encoding only the mandatory "
, it remains readable:
My name is "Joe"
Whereas if you encode everything, the HTML source becomes unreadable (though it would display perfectly in the browser itself):
My name is "Joe"
Marketo uses minimal encoding like PHP’s htmlspecialchars(), so just the 4 characters <
>
"
&
are included. This works perfectly for text content. But here’s the rub: code in a <script>
isn’t text content. It needs a different type of encoding.
What is JS encoding?
Encoding for JS is strikingly different from HTML encoding.
- some characters happen to be special in both contexts, but they can’t be encoded the same way in JS
- some characters aren’t special in HTML but are special in JS and must be encoded
- some characters are special in HTML but aren’t in JS, so they must not be encoded
- there are even different encoding rules in JS for the same character
For one example, the double quotation mark "
is special in both HTML and JS. But you encode it as \"
in a JS string, not as "
. If you encode the wrong way, your code won’t break at the syntax level, but it will produce poor results. My name is "Joe"
will be shown as My name is "Joe"
and at the very least look unprofessional.
For another, the ampersand &
is special in only HTML. If you output it as &
in JS, you get those 5 literal characters &
a
m
p
;
as they have no special meaning. The end user sees Tom & Jerry
instead of Tom & Jerry
.
For yet another, a line break isn’t special in HTML. But in JS it’s extra special. Inside a double- or single-quoted string, it must be encoded as \n
. While inside a backtick-quoted string, you may encode, but it’s optional. And outside of strings, you must not encode it. Confusing, eh?
In sum: you can’t output generic HTML-encoded text into a <script>
.[2]
Solution: output into <datalist><option>
elements
The right move is to output {{lead.tokens}}
into a hidden element, then read them out using JS. This eliminates any encoding worries because JS sees the decoded value.
Technically, any element will do, but I find the perfect fit is a <datalist>
with nested <options>
:
<datalist class="mktoTokens">
<option label="lead.Channel">{{lead.Channel}}</option>
<option label="lead.Country">{{lead.Country}}</option>
</datalist>
A <datalist>
is always hidden, and semantically it represents a list of data points. Close enough if you ask me!
As you can see, the label
is the token name without the {{
}}
; the text is the token itself, with the {{
}}
).
(You can actually use any label
, I just find it easier to be consistent.)
Reading from a <datalist>
: getExportedMktoTokens()
Here’s a simple function getExportedMktoTokens()
to fetch one or more tokens:
function getExportedMktoTokens(tokenLabels){
const stor = "datalist.mktoTokens option[label]";
const options = Array.from(document.querySelectorAll(stor));
const ignoreCaseComparator = Intl.Collator(undefined, { sensitivity: "accent" });
let results = [];
for( const name of [tokenLabels].flat() ){
const value = options.find( option => ignoreCaseComparator.compare(option.label, name) === 0 )?.value;
results.push(value ?? null);
}
return Array.isArray(tokenLabels) ? results : results[0];
}
You can call it with one token label or an array of labels:
let leadCountry = getExportedMktoTokens("lead.Country");
let [leadCountry, leadChannel] = getExportedMktoTokens(["lead.Country","lead.Channel"]);
As you might’ve picked up, the <option>
search is case-insensitive to align with how tokens work. So getExportedMktoTokens("LEAD.COUNTRY")
will get the token in <option label="lead.Country">
.
That’s it! Now you’re ready to safely use Marketo tokens in JS.
P.S. A happy side effect is you can also reference tokens from external JS files, which otherwise can’t use tokens.
Notes
[1] You actually can use a literal NBSP in HTML if you copy and paste it from somewhere. It’s just impossible to distinguish from a regular space, so you’re likely to delete it accidentally.
[2] Theoretically, you can output variables into JS that pass through an HTML encoder if you know with 100.00% certainty they don’t contain any characters that are special in HTML or JS — that is, no HTML encoding will happen and they don’t contain any special JS characters. But you cannot know this with a Marketo string field!