Skip navigation
All Places > New York User Group > Blog > Author: Sanford Whiteman
1 2 3 4 Previous Next

New York User Group

60 Posts authored by: Sanford Whiteman

The semi-secret email variable ##MKT_TOK## allows you to do something that’s otherwise impossible: create a link that isn’t tracked (rewritten to bounce off your branding domain) but is mkt_tok-enized so it still associates leads.

 

In other words, a link that is altered only by having the mkt_tok appended to the query string.

 

In the standard email body, you do this like so:

 

<a class="mktNoTrack" href="https://www.example.com/path/to/my/page.html?mkt_tok=##MKT_TOK##">Click me</a>

 

 

If you generate links in Velocity, though, that curiously-formatted ##MKT_TOK## doesn't exist. But it has a direct Velocity equivalent, $mktmail.MKT_TOK. So add that instead in VTL:

 

<a class="mktNoTrack" href="https://www.example.com/path/to/my/page.html?mkt_tok=${mktmail.MKT_TOK}">Click me</a>

 

That’s it, shortest post in a while!

The {{system.viewAsWebPageLink}} token always points to your Primary LP domain. But you want View As Web Page (VAWP) to use the Domain Alias for each brand or other subtenant in your instance.

 

Though the path to the VAWP service is clearly exposed – it’s /index.php/email/emailWebview and it exists under any domain – you can’t simply hard-code the domain of your choice and be on your way.

 

The problem is the specific type of mkt_tok value that must be in the query string. It’s a weird quirk: until the redoubtable Courtney Grimes discovered it, there was no (public) knowledge that mkt_tok came in different types!

 

Say your Primary LP domain is pages.example.com and the Domain Alias you’re going for is pages.example.org, your non-profit side.

 

If you include this link:

 

<a href="https://pages.example.org/index.php/email/emailWebview">View as Web Page</a>

 

Then Marketo will add a mkt_tok to the link automatically (like any tracked link) but it’s the wrong type of mkt_tok. Quoting from a Marketo case:

 

the mkt_tok value generated for view as webpage tokens is slightly different than the mkt_tok value generated for links in emails and Velocity scripts

 

More confusing, the generic mkt_tok may appear to work fine when you’re spot-checking emails, but apparently some types of personalization (segments? COs? I don’t know the details) will fail.

 

So, tempting as it is, “just” hard-pointing to the Domain Alias, though it works perfectly for LPs themselves, won’t work for the VAWP service.

 

The workaround

Luckily, we have a perfect storm of hackability, if you will.

 

  •  {{system.viewAsWebPageLink}} always includes the correct mkt_tok, regardless of where it appears in the email (hint, meaning it is pre-mkt_tok-enized even if it's not the direct target of an <a href>)
  • {{system.viewAsWebPageLink}} doesn’t include any characters that would need to be URL-encoded if they appeared in the hash part of a URL.

 

As a result, we can append the {{system.viewAsWebPageLink}} to the URL of a dedicated redirector page  (a Marketo LP).

 

The redirector page is visited via the desired Domain Alias. Then it immediately redirects to the VAWP service under that same hostname.

 

Since that was probably opaque, I mean a URL like this:

 

https://pages.example.org/brandView#https://pages.example.com/index.php/email/emailWebview?mkt_tok=eyJpIjoiWkdabE56TXpNalUzTjJKbS...

 

Gets redirected to one like this:

 

https://pages.example.org/index.php/email/emailWebview?mkt_tok=eyJpIjoiWkdabE56TXpNalUzTjJKbS...

 

OK, OK. The Primary LP domain still appears somewhere in the first URL. So in technical terms, yes, it’s still there. But the hostname shown when you hover over the link is the Domain Alias.

 

The redirector page HTML

The entirety of the redirector page (Guided LP template) is:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Loading Web View...</title>
<script>
(function(redirectTarget){
if( !redirectTarget ) return;

var systemVAWP = document.createElement("a"),
customVAWP = document.createElement("a");

systemVAWP.href = redirectTarget;
customVAWP.href = document.location.href;

customVAWP.pathname = systemVAWP.pathname;
customVAWP.search = systemVAWP.search;
customVAWP.hash = "";

document.location = customVAWP;
})(document.location.hash.substring(1));
</script>

</head>
<body>
</body>
</html>

 

Publish an LP based on that template (here brandView.html), then link to that LP under the preferred Domain Alias, using the no-track class:

<a class="mktNoTrack" href="https://www.example.org/brandView#{{system.viewAsWebPageLink}}">View as Web Page</a>

 

 

Completely unnecessary enhancement

You can animate the document title while redirecting, even though it should take a fraction of a second:

 

 

Here’s the JS for that:

// Optional, just animating the ellipsis
(function(){
var numDots = 0,
maxDots = 10,
dotDelay = 250;

function appendDot(){
if ( numDots++ < maxDots ){
document.title += ".";
setTimeout(appendDot, dotDelay);
} else {
console.log("Please-wait dots exceeded, possible timeout of next page?";
}
}

appendDot();
})();

 

This is merely a frill and doesn’t change the actual logic.

 

Choose your Primary LP domain wisely

This situation should be a reminder: choose a Primary LP domain that’s okay for everyone in your instance to see – even though we can minimize its exposure, there are still places where it’s visible, even if just for a half-second, or via view-source:, or until someone remembers to write some Velocity to switch it out.

 

If you’re running a “concierge” instance serving multiple sub-clients, you don’t want to dedicate the Primary domain to any one client. And if you want to obscure your own agency’s name, you should register a generic domain name along the lines of marketo-hosted.solutions.

 

If you’re running multiple brands then the Primary can be the umbrella company, or if that’s not supposed to be shown, a generic domain like the above.

You might not have noticed that {{lead.token}} values are HTML-encoded in Landing Pages. (Upfront note: this is A Good Thing, since it’s the secure-by-default approach.)

 

So if you have a Text Area field (same goes for String):

 

 

And you just drop that into an LP:

 

 

You’ll see the exact textual representation in the approved page — that is, in this case, you will not have a clickable link:

 

 

Of course, this is because the underlying HTML source is a “dead” text representation of the value, not a “live” <a>:

 

 

There isn’t anything wrong with this default behavior and it’s far less dangerous than rendering HTML by default.

 

But if you need to “resuscitate” such tokens — only if they’re from a trusted source — first make sure they’re placed inside easily identifiable HTML containers. In this case I’m taking a page from the Marketo Email Editor and setting the data-allowhtml attribute on a surrounding <span>:

 

 

Then add the related JS to the <head> of the LP:

 

document.addEventListener("DOMContentLoaded",function(e){
var arrayify = getSelection.call.bind([].slice);
arrayify(document.querySelectorAll("[data-allowhtml='true']"))
.forEach(function(htmlable){
htmlable.innerHTML = htmlable.textContent;
});
});

 

And now it’s a true-and-living <a>!

 

 

You might want to hide (display: none) the elements until they’re resuscitated but I leave that to you.

 

Only Built for Trusted Linx

Be careful with this workaround, though: only use it if you’re 100% sure the data and the output context can be trusted. If the value was populated via form fillout and never sanitized, it is definitely not trusted! While if it was synced over via a managed integration, it might be trusted.

Using the Lead ID from Velocity is trickier than you'd expect ($lead.Id is not set automatically, even though there's a {{lead.Id}} token outside VTL).

 

And there are other interesting fields that are handy to have at send time a couple of which aren't accessible any other way, giving Velocity a leg up.

 

Here's a quick snippet that creates a $marketoSend object with properties for Campaign ID, Campaign Run ID, and more:

 

#set( $marketoSend = {} )
#set( $delim = ":|-" )
#set( $values = $mktmail.xMarketoIdHdr.split($delim) )
#set( $numParts = $values.size() )
## Campaign Run ID not relevant in Preview, Test Variant not always present, etc.
#set( $keyTemplatesBySize = {
   13 : "MunchkinId-+MunchkinId-+MunchkinId::CampaignId:StepId::AssetId::CampaignRunId:LeadId--TestVariantId",
   12 : "MunchkinId-+MunchkinId-+MunchkinId::CampaignId:StepId::AssetId::CampaignRunId:LeadId-",
   11 : "MunchkinId-+MunchkinId-+MunchkinId::CampaignId:StepId::AssetId::CampaignRunId:LeadId",
  "*" : "MunchkinId-+MunchkinId-+MunchkinId::CampaignId:StepId::AssetId::LeadId"
} )
#set( $marketoIdKeys = $display.alt($keyTemplatesBySize[$numParts],$keyTemplatesBySize["*"]) )
#set( $keys = $marketoIdKeys.split($delim) )
## loop interesting keys
#foreach( $key in $keys )
#if( !$key.isEmpty() )
#if( $key.startsWith("+") )
#set( $finalKey = $key.substring(1) )
#set( $marketoSend[$finalKey] = $marketoSend[$finalKey] + "-" + $values[$foreach.index] )
#else
#set( $finalKey = $key )
#set( $marketoSend[$finalKey] = $values[$foreach.index] )
#end
#end
## append Program ID
#set( $marketoSend.ProgramId = $program.values().iterator().next() )
#end
Munchkin ID is ${marketoSend.MunchkinId}
Program ID is ${marketoSend.ProgramId}
Campaign ID is ${marketoSend.CampaignId}
Campaign Run ID is ${marketoSend.CampaignRunId}
Step ID is ${marketoSend.StepId}
Asset ID is ${marketoSend.AssetId}
Lead ID is ${marketoSend.LeadId}
Test Variant ID is ${marketoSend.TestVariantId}


You can then add the variables to a URL (also built in Velocity) like so:

 

<a href="https://www.example.com/pagename.html?c=${marketoSend.CampaignId}&cr=${marketoSend.CampaignRunId}">Click me</a>

ss

ss

 

Make sense? I'm sure it does.

 

But you might wonder why you can't include the <style>, or a remote <link> for that matter[1] directly in the mktoText area, like so:

 

ss

 

Test and you'll see why Text {{my.tokens}} are necessary.

 

 

Another take

A twist on the same approach gives something superior, if a little strange(r).

 

Set up just 2 tokens, one for opening <style> and one for the closing </style>, at the top level of Marketing Activities. Then you can reuse those tokens everywhere and write CSS directly in the mktoText.

 

ss

ss

 

 

 

 

Notes

[1] You can include a <link> tag in the {{my.token}}, of course. I'm not saying you can't use external stylesheets, just that they need to be passed in via tokens.

Spinning this off the earlier Part I (on number types) because it's an equally important topic.

Not necessarily a rivetingly interesting topic, but since some of you are trying to move into more technical roles, these are good things to know to geek up your conversation. Knowing these things before I became a full-time developer, having learned them on the SQL database side, made coding more comfortable for me.

So: very bad things happen when number field types are used to store values that aren't truly numbers, but actually numeric strings.

What's a numeric string?

Numeric strings are sets of digit characters (0-9) that do not actually represent a single number, or perhaps we could say are not actually equal to their apparent number value.

Those seemed vague, I know. Examples are better teachers…

Credit card numbers are numeric strings

Credit card “numbers” (formally, payment card numbers) are numeric strings. They are a set of independent codes mashed together (MII digit + network number + your number + checksum digit), like:

4110144110144115 
│└───┘└───────┘│

But they do not represent a 16-digit integer: this is not the value 4,110,144,110,144,115 (4 quintillion something) and should never be thought of that way.

Yet some people still ***** up and store CCs as numbers, thinking it'll be more space efficient. It certainly would be — if it worked — since a char(16) takes 16 bytes while a 64-bit int takes only 8 bytes. The ability to reduce your storage requirements by 50% is really tempting. But it's wrong in more ways than one.

The most basic way it's wrong is if you don't actually have a big enough datatype everywhere this value will be used. In the previous post on number types, you saw that the maximum safe value of an SFDC Double is 9,007,199,254,740,991. OK, so 9 quintillion. The above credit card (mis)represented 4 quintillion will fit into that. But what about this card number:

9792004110144115 
│└───┘└───────┘│

Nope! That's outside the Double range. And that's not surprising, as card numbers are not in any way calibrated to fit in a certain data type.

Note I chose a 16-digit credit card number above. In fact, a Visa number, among others, can be 19 digits. So that blows a Double out of the water completely and might make you contemplate a 64-bit Integer (as long as you have end-to-end support for that type). Sorry, won't work either:

9792004110144111248 
│└───┘└──────────┘│

That value, if you attempt to store it as a gigantic integer, won't even fit in a 64-bit int, which has a max value of 9,223,372,036,854,775,807.

James Bond's agent number is an alphanumeric string

While daydreaming about, er, “real-world” examples, 007 came to mind.

 

Read the full post on

After keeping this code close to the vest since 2015 (!), it's time to share the knowledge.

On the Community, I periodically refer to “my method” and to how other all other approaches are horribly hacker-friendly (true!). But I realize it's been a ludicrously slow reveal.

I'm still keeping my advanced method (for those who can't meet the below prerequisites) closed-source, but the simple method here will work for the vast majority of users.

The prerequisites

Here are the prereqs to use the open-source code:

❶ Your external site must share a private parent domain with one of your Marketo instance's LP domains or domain aliases. That is, if your external site is http://www.example.com then you'll need to have (or newly create) a Marketo domain like http://pages.example.com. Those share the private parent example.com.

Such a domain is usually already available. A domain like http://pages.example.info doesn't share a common parent with http://www.example.com so you can't use that. You can either use an existing domain or create an alias for this specific task, so http://special-forms-stuff.example.com is fine to use instead of your primary http://pages.example.com.

If and only if your external site runs over SSL (https://) then the Marketo LP domain must also run over SSL. But there's no requirement in reverse: it's fine if your LP domain runs over SSL but the external site doesn't. And it's also fine if both of them are still plain http://.

❸ Almost not worth mentioning: you need to have a server that can host a single JS file. You can't upload JS to your Marketo file library, because Marketo still (!!!) doesn't serve the .js extension correctly for all browsers. But, pretty much by definition of needing external site pre-fill, you must have another server, right?

 

Read the full post on

When choosing a Thank You/Follow Up URL (wish there were only one term in use!) Form Editor presents 3 largely self-explanatory options:

ss

Choosing Landing Page will give you a nice dropdown of all your approved Marketo LPs.

Choosing External URL and entering the full URL of a Marketo LP seems to work, too — but it's a bad move. Form Editor won't tell you why, but I will.

The problem

If you choose Landing Page or Stay on Page, Marketo uses a special read-your-write feature to ensure Pre-Fill on the next pageview.[1]

If you choose External URL, Marketo looks up the URL in your known LPs but it doesn't look in your Redirect Rules.  If the URL is not seen as a core LP URL, even if it does lead to a Marketo LP in reality, then the read-your-write Pre-Fill cache is not enabled.

What is “read-your-write” and why should I care?

“Read-your-write” or RYW is a desirable, but not always supported, feature of distributed systems. (A distributed system is any system that processes data at multiple tiers, like we all know Marketo does.)

RYW, in a nutshell, means:

If a user thinks they've made a change to the back end, then show them the new data as if it's been fully saved, regardless of whether system(s) may be still filtering and processing — or even discarding! — the data in the background.

It doesn't mean anybody else sees uncommitted data (there are some cases in which it won't be saved, so you don't want to propagate bad info more widely than necessary).

It means that for a less confusing user experience, you let a user see their own requested update immediately, instead of forcing them to see older saved data for a few seconds/minutes (even though showing saved data would technically be more accurate end-to-end).

Marketo implements read-your-write via the special query parameter aliId. It's a numeric identifier for a set of submitted form values, and it's usable regardless of whether the values are actually saved. When a page has an aliId in the URL, it's capable of (for the most potent example) filling a second form using the data from a first form, even if it was submitted mere milliseconds before.

Back to Form Editor

When you choose External URL in Form Editor, Marketo tries to append the aliId intelligently, but it can't know about your redirects (especially 3rd-party redirects) or anything that might disguise the Marketo-ness of the destination URL.  As a result, the next LP someone views may show their session's old data (maybe including someone else's email) or no data at all (the session still being effectively anonymous). You don't want that! So choose Landing Page if it's indeed a Marketo LP.

Other complications

When you use the Forms JS API onSuccess method to choose a Thank You URL dynamically (a very powerful option) set the form to Stay on Page in Form Editor, as this will ensure the aliId is appended.

Then clip out and append the original query string, which will include the aliId, to the dynamic URL.

A quick-and-dirty way to append the original query is like so:

MktoForms2.whenReady(function(form){
  form.onSuccess(function(vals,tyURLFromFormEditor){
    var originalThankYouDoc = document.createElement("a");
    originalThankYouDoc.href = tyURLFromFormEditor;
    
    var dynamicThankYouURL = "https://pages.example.com/other-marketo-lp.html" + 
      originalThankYouDoc.search;
    
    document.location.href = dynamicThankYouURL;
    return false;
  });
});

If you're using a fuller-featured URI parser/builder already, like uri.js, use that instead of the Link/Location method (though the location.search breakdown works perfectly, since it's a key browser function).

Just don't write your own parser… I don't want to have to holler at you again!


NOTES

[1] When you use Stay on Page the aliId is attached even if you're using the form embed and the page is a non-Marketo page. It's not fully honored in this case (since Pre-Fill isn't supported with the embed) but it's better to have it there than not.

You should already know that variables don't work in the Text version of Marketo emails. And you've likely dismissed this as a head-shakingly short-sighted mostly harmless detail.

But it has one wider consequence, as user JK noticed in this Community thread, that's firmly a bug — and one which impacts the email as a whole.

The documented (if suboptimal) behavior

Variables on the Text side are typically replaced with blank output. This can either:

(a) mangle the Text version, if variables happen to contain critical content, or
(b) be fine and dandy, if variables hold colors or other styles that pertain only to the HTML side anyway

Outcome (a) is not great, but at least you know to plan for it.

The undocumented (and bad) behavior

But when a ${variable} is used as a link, like…

<a href="${link}">Click for fun in the sun</a> 

and uses one of the 20 or so Velocity reserved words, ya got problems. Because of the additional layer of processing for links (to be rewritten to the branding/tracking domain) you get everybody's favorite WTF:

ss

And this happens regardless of whether you used any Velocity tokens!

(Of course I mean any userland {{my.tokens}} because Velocity is always used to assemble Marketo emails, even if you don't write any code yourself. Didja know that? Or maybe suspect it?)

Anyway, the solution is to not use any of these words as${variable} names (and tell your template designer, too):

alternator 
class
context
convert
cookies
date
display
esc
field
import
include
link
loop
math
mktEncrypt
my
number
pager
params
render
sorter
text
xml
GENERIC_TOOLS_AVAILABLE
STRUTS_TOOLS_AVAILABLE
TOOLS_VERSION
VIEW_TOOLS_AVAILABLE

And that's about all I have to say about that.

There are 2 ways to access references (i.e. variables) in Velocity.

 

(1) Simple/shorthand notation, prefixed with a $:

 

$variableName
$variable.property
$variable.method()
$variable.property.method()

 

(2) Formal/longhand notation, prefixed with $ and wrapped in {} (curly braces):

 

${variableName}
${variable.property}
${variable.method()}
${variable.property.method()}

 

Simple notation should be your default. Formal notation should only be used when there's no alternative. As the Velocity docs say:

 

In almost all cases you will use the shorthand notation for references, but in some cases the formal notation is required for correct processing.

 

I'd go further: when used unnecessarily, formal notation can make your code confusing and fragile. Unfortunately, Marketo's script editor doesn't help you learn best practices, because when you drag a field from the field tree to the editor canvas, it's automatically  wrapped in ${}:

 

5acbd8e898b1d0002262929b_vtl_formal_scripted.png

You should remove the curly braces right away, and only restore them if it proves necessary during development.

 

What can go wrong?

A recent Community post shows how confusing things can get when you use formal notation unnecessarily (note how the poster titled it "... tokens behave unexpectedly" when it's actually established behavior, if not well-circulated).

 

The catch: a reference enclosed in ${formal.notation} cannot be chained with a method/property outside the curly braces.

 

OK, that probably didn't make sense unless you're fluent in OO-speak! Let's look at some examples.

 

This does work with simple notation:

 

#if( $lead.FirstName.isEmpty() )

 

It doesn't work if you only enclose part of the expression in formal notation:

 

#if( ${lead.FirstName}.isEmpty() )

 

Sure, it would work if you happened to enclose the entire expression in curlies

 

#if( ${lead.FirstName.isEmpty()} )

 

but you should just use simple notation instead, because it's harder to mess up during refactoring.

 

Don't believe me? (You probably do, or I'd like to hear who's more authoritative about Velocity.) Consider what happens when you move from a not-best-practice, but syntax-error-free, comparison using double-equals ==:

 

#(if ${lead.numberOfProducts} == 0 )

 

to a more forward-looking equals():

 

#if( ${lead.numberOfProducts}.equals(0) )

 

Suddenly, your Velocity token stops working because you can't have a . after a }. It may be clear, to you as a human, what you meant, but VTL doesn't allow it. If you'd used simple notation from the start you wouldn't have to make such adjustments.

 

When do you need to go formal?

Formal notation should only be considered inside strings or output, and only used when the separation between variables and static text would otherwise be unclear.

 

If you're in a line of code starting with #if or #set chances are very slim that you should be using formal notation.

 

Here's one case where it is necessary...

 

Read the full post on

You've seen before that the Forms 2.0 input mask plugin can be tweaked to do some more elegant stuff.

Here are a few more things you might want to do. I only recommend masks for things that have an explicit standard (like phone numbers, credit cards, etc.). But if you're going to use them for more than that, use them wisely!

The code is bundled at the bottom of the post.

SUPPRESS THE UNDERSCORE AS THE PLACEHOLDER

By default, the mask shows a _ character in every empty position up to the max length.

This can look ugly, especially if there's already a hint in the field telling people it's limited to N characters.

ss

You can remove the placeholder character entirely or replace it with something else (though I'm not sure anything is better than underscore or blank in this case, maybe a cursor block like ░ if you want to be retro-cool?).

ACCEPT ANY LATIN-1 LETTER, NOT JUST A-Z AND a-z

When you select the a shortcut in the Form Editor…

ss

… you're actually blocking all but the slimmest subset of alphanumeric characters.Even the e-with-acute-accent in Grégoire is blocked! That's not good when words contain these slightly out-of-the-American-ordinary characters.

It works this way because, under the hood, input masks use the regex character class [A-Za-z] to implement a. And that's ASCII unaccented letters only.

The better move is to use a class like [A-Za-z\u00C0-\u024F] which includes Latin accented letters, i.e. those from Romance languages. (You'll still be blocking people who spell their names using other glyphs, but that's another matter.)

Better still, if you're in the world of names, allow hyphens, apostrophes, periods, and spaces: [A-Za-z\u00C0-\u024F'. -].

ALLOW SPACES, NOT JUST CHARACTERS

When you set a mask to **********, even if you don't want non-ASCII, non-accented characters, do you really mean that spaces aren't allowed? Sometimes yes, sometimes no.

In the case of ISBN-10s, for example, no spaces are allowed per the international standard, and you might want to block spaces in the Phone field for standardization.

But in the case of First or Last Name, Company, or tons of other cases, you certainly don't want to block spaces.

MAKE IT HAPPEN

All of the above functions can be enabled with the helper JS below.

First, in Form Editor, give each field you want to re-mask an initial mask (any mask pattern will do). (If it isn't masked to begin with, we can't tweak the mask setup.)

Then add your custom patterns to the inputMasks array as below: the name property is the Marketo form field name and the rest is explained in the comments.

 

(function() {
   MktoForms2.$("body").on("mkto_inputmask_polyfilled", function(e) {
      var inputMasks = [
         {
            name: "Title",
            maskPattern: "ssssssssss", // 10 [A-Za-z ] letters or spaces
            maskCharPlaceholder: "" // no placeholder char
         },
         {
            name: "Nickname",
            maskPattern: "nnnnnnnnn", // 10 [A-Za-z0-9 ] chars or spaces
            maskCharPlaceholder: "" // no placeholder char
         },
         {
            name: "Interest",
            maskPattern: "cccccccccc", // 10 Latin letters or (some) Latin puncuation marks
            maskCharPlaceholder: "░" // alternate placeholder char to show how it's done
         }
      ];

      /* --- NO NEED TO TOUCH BELOW THIS LINE! --- */

      MktoForms2.whenReady(function(form) {
         var maskCharExtensions = {
            c: "[A-Za-z\u00C0-\u024F'. -]",
            s: "[A-Za-z ]",
            n: "[A-Za-z0-9 ]"
         };

         Object.keys(maskCharExtensions)
            .forEach(function(char) {
              MktoForms2.$.mask.definitions[char] = this[char];
         }, maskCharExtensions);

         inputMasks
            .map(function(field) {
               field.el$ = form
                  .getFormElem()
                  .find("[name='" + field.name + "']");
               return field;
            })
            .forEach(function(field) {
               var mask =
                     typeof field.maskPattern != "undefined"
                        ? field.maskPattern
                        : field.el$.data("mktoInputMask"),
                  placeholder =
                     typeof field.maskCharPlaceholder != "undefined"
                        ? field.maskCharPlaceholder
                        : MktoForms2.$.mask.placeholder;

               field.el$.mask(mask, { placeholder: placeholder });
            });
      });
   });
})();

Boolean type fields enjoy a strange place in Marketo's Velocity implementation.

On the Marketo UI side and via most Marketo APIs, Booleans hold consistent true/false values (presumably using SQL bit type fields under the hood).

But in Velocity, as I've explored before, a Boolean field is presented as one of two Strings:

  • "1" for Boolean true
  • "" (empty string) for Boolean false

That conversion is actually really confusing.

See, Velocity inherits a lot of things (variable handling things) from Java.

In Java, a non-null String object, on its own, is always evaluated as Boolean true. Regardless of whether it has any characters in it (that is, regardless of whether it's empty).

So if you do:

#if( $lead.someBooleanField )
Field is true
#else
Field is false
#end

You will always get “Field is true” even if you've unchecked the corresponding checkbox in the Marketo UI.

If Marketo Booleans were Velocity/Java Booleans, on the other hand, this would've worked fine. But alas.

So the question is what to do with these sort-of-Boolean-like-Strings (whose meaning we understand, but the VTL language doesn't). What do we convert them to to make them more usable?

You can do a step-by-step conversion to Boolean by doing a String comparison:

#if(  $lead.someBooleanField.equals("1") )
#set( $lead.someBooleanField == true )
#else
#set( $lead.someBooleanField == false )
#end

After that conversion, #if($field) will work as expected.

But I've been thinking lately that maybe converting those Strings to Numbers — 0for false and 1 for true, as the standard goes — gives us more juice within Velocity than if we were to go with true Booleans.

 

 

Read the full post on TEKNKL :: Blog →

GDPR and related regulations call for some fancy footwork with activity tracking.

 

Leads may be members of the same program, set to receive the same email but many have opted-out of tracking, while the rest agreed to be tracked for the greater good.

So you need a single email to go out, but some get the tracked version of links (bounced off your branding domain) while others get the raw links (straight to the URL). Same for the <img> that tracks opens: some get the pixel, others don't.

The solution, as for so many things, is Velocity.

If canBeTracked is your Boolean lead field, first convert it to a number:

#set( $link = "https://info.example.com/somepage.html" )
#set( $canBeTracked = $convert.toNumber("0${lead.canBeTracked}") )

Then you can use this VTL logic to selectively track links:

#set( $trackingClass = "{0,choice,0#mktNoTrack|1#}" )
<a class="${display.message($trackingClass, $canBeTracked)}" 
   href="http://www.example.com/page.html">Le Click, C'est Chic</a>

That'll output a tracked link for those that opted in, an untracked link for everyone else.

To selectively track opens, first disable open tracking in Email Settings:

5abc6e1a73da1100229befee_vtl_trackswitch_disableopen.png

Then manually include the open-tracking pixel in the template:

#set( $trackingLink = "http://click.example.com/trk?t=1&mid=${mktmail.QpMarketoId}" )
<img src="${display.message("{0,choice,0#|1#${trackingLink}}", $canBeTracked)}">

 

What's that $display.message() thing?

I've explained the code internals in a separate post to keep this one short!

     Velocity is the only language where Brevity for Brevity's Sake is OK.

     In other languages, reducing total lines of code (LOC) with show-offy shortcuts just means your code will make no sense (to you or anyone else) after a few months away.

     But in Velocity, the shortest possible implementation is a smart move, since:

    • VTL is verbose (only one major operation permitted per line, temp variables required even for void methods)
    • it offers limited reusability (especially Marketo's flavor of Velocity, where #macro and reused $references don't work consistently)
    • it's completely whitespace-preserving (indenting would help readability, but messes up output — so the fewer lines, the less to mess up)
    • the more lines of code in your template, the more distraction from the “meat,” which is the output

     Imagine you had a few Lead fields using the JETDS naming system: installationEnvironment, typeOfEquipment, and purpose.

     There are 10-20 options for each of these fields, so the number of total combinations is huge. Even if you're only interested in a subset, the conditions get loooooong...

 

 

Read the full post on TEKNKL :: Blog →

     This is more like a pre-Velocity tip, since you need to make this decision while setting up your Marketo and/or SFDC Custom Objects, before you even try to read them using Velocity.

     Neither SFDC nor Marketo will stop you from creating such COs, and Marketo will even show them in the script editor:

ss

     But your Velocity scripts will always error out if you use those objects (with the unhelpful error message String index out of range -1).

     The reason?

 

 

Read the full post on TEKNKL :: Blog →