Isn't it annoying to add a Wait step before sending a registration confirmation, crossing your fingers that the provider has phoned home with the {{Member.Webinar URL}} token?

I've always hated this step. Not only because of the experience for the end user, who might think you've forgotten about them, but on a pure technical level. Techies hate timers that are “probably sufficient”: we want tools that are predictable, not best-case guesses with fatal downsides. Not only might a 15 minute Wait not be sufficient under load, there's no Wait that will fix a major problem with the integration, so users will eventually get an email with a big blank in the middle anyway.

What if I told you you don't need to hold off on such emails at all?

Instead, set up an LP in the webinar program that redirects to the {{Member.Webinar URL}}. Send them a link to that LP with no delay. Unless it's very close to the webinar's start time, most people aren't going to click a Join Webinar link right away, so you've got lots of breathing room for the API to sync up the {{Member.}} token.

When they do click the email, you'll be ready for them and will immediately bounce them over to the webinar, hands-free. If it does happen that the URL isn't yet ready when they click, well, you weren't going to get it to them any quicker with an arbitrary delay! You can give them a countdown timer and reload the page in 30 seconds, and you can send them a follow-up email since you know they're eager (that second email might as well have a Wait step).

<div class="webinar-pending-note">
Your customized webinar URL is still being assembled.
Please refresh this page or re-click the email link in a few minutes!
</div>

More ideas

Once the LP is in place, you can extend the user experience in several ways:

1. As noted above, you can add a periodic document.location.reload(true) in case they did hit the page before the API phoned home.

2. You can adapt some of my Redirector Page JS to add a progress indicator before reloading. Note sending leads to the Webinar Redirector LP also enables Munchkin tracking, which is a benefit in its own right.

3. You can add AgicalAdd to Calendar links to the page, referencing the {{Member.Webinar URL}} using the alternate separator syntax originally designed for this very case.

4. If you want to send the lead an Add to Calendar link that works from the start, then reference the Redirector LP's URL in your .ICS file or Agical link, not the {{Member.Webinar URL}}.

5. You can retarget the lead with rich contextual content if they land on the page after the webinar is over (check another {{my.}} token that stores the event date) or well before it begins, instead of taking them to this beautiful page:☺

For any Velocity project, I've taken to offering clients a separate config token, call it {{my.ThisIsWhereYouChangeStuff}}, where they can manage some output settings without having to email me all the time.

Then there are one or more {{my.PleaseDontChangeAnythingInHereItsFragile}} tokens with the meat of the code.

Velocity #define directives are really handy for the config token. They're a bit more fault-tolerant than #set statements, where the non-technical person has to remember to escape quotes, close parentheses and such.

Which is bad because it will wreck your links — even though you may not even see the wreckage in Preview because of the way HTML itself swallows line breaks.

Now, you can suppress the trailing whitespace by adding a comment ## at the end of the line…

https://www.example.com/preferencecenter##

… but I daresay that's not an improvement, since the idea is to offer this token as a not-too-fragile place for a non-technical person to make adjustments, and adding ## is something they're bound to forget or mess up.

So what you want to do is let them enter text in as close to free-form fashion as possible. Then in your code, strip out extraneous whitespace at the beginning or end to be tolerant of minor messups.

How trim() works

The documentation of the trim() method in Java, which exists on any Java String — and therefore on any Velocity String — is almost lovable in its complexity.

trim() does exactly what we want, but you have to understand the ASCII table to know that! Not that a programmer shouldn't understand ASCII, but it's a particularly circuitous explanation IMO:

[L]et k be the index of the first character in the string whose code is greater than '\u0020' (the space character), and let m be the index of the last character in the string whose code is greater than '\u0020'. A new String object is created, representing the substring of this string that begins with the character at index k and ends with the character at index m-that is, the result of this.substring(k, m+1).

Let me put that in clearer terms:

If a contiguous block of characters between ASCII 0 and ASCII 32 is found at at the beginning and/or end of the string, the whole block is removed.

ASCII 0 through ASCII 32 means the nul (0) through space (32) characters, inclusive. In that range are the quite common carriage return (13), line break (10), and tab (9) characters, and some more obscure ones like vertical tab (11).[1]

So though it only explicitly mentions the space character \u0020 (hex 20 is decimal 32), which you're probably familiar with as %20 in URLs, in fact it covers line breaks as well. If there's a long intro or outro of spaces, line breaks, and tabs, trim() will clean 'em all out.

trim()-ing what's inside a #define

So trim() is perfect, but you can't simply do this:

<a href="${baseURL.trim()}">${linkText.trim()}</a>

That'll throw an error. The reason is that any #define, when you address it directly, is a Velocity-specific Block$Reference, not a generic java.lang.String.

A Block Reference doesn't itself have a trim() method. But it does have a toString() method. (In fact, toString() is called under the hood when you output a plain ${reference} in Velocity, otherwise you couldn't output it at all.)

So — I know this was long-winded but hopefully you learned something — you need:

Notes

[1] But not all whitespace characters, since some as common as non-breaking space (the famous &nbsp; in HTML) are above ASCII 32. And over in JavaScript, the almost-identically-purposed trim()does strip non-breaking space. Is there nothing in programming that's not complicated when you care to learn the details? ☺

"I am thrilled to announce that Marketo has entered into a definitive agreement to be acquired by Adobe. Adobe and Marketo both share an unwavering belief in the power of content and data to drive business results. Together we will deliver an unrivaled solution that will place customer experience and engagement at the heart of digital transformation. This announcement is a momentous occasion for Marketo, as it signals the next phase of our company’s growth."

For more information about the pending acquisition, please read Steve Lucas' full blog and Adobe's press release below.

Integers are number-like Strings (which can wreak havoc on comparisons if you don't realize it).

Boolean fields are boolean-ish Strings. They have the possible values "" (empty String) if the original Boolean was false and numeric String "1" if true.

But the same doesn't hold for Boolean fields on other objects besides the Lead.

Booleans on Oppties and Salesforce COs

On Opportunities and other SFDC Custom Objects, bizarrely, Booleans become a different type of String, which has the possible values "0" for false and "1" for true (this is admittedly a more traditional conversion than empty String and "1" but why, oh why, must it be different?).

Booleans on Marketo COs

On Marketo Custom Objects, Booleans are presented as true Booleans! As in real Java Booleans with the reserved values true and false, the way we dream they'd be everywhere.

Wow, that's confusing!

Uh-huh. As I'm sure you realize, such differences all have to be considered for sorting.

So the 3 questions EC was implicitly asking were:

(1) How do you sort objects based on a String field that can be empty string "" or numeric string "1", such that "" comes first?

(2) How do you sort objects based on a String field that can be numeric string "0" or numeric string "1", such that "0" comes first?

(3) How do you sort objects based on a Boolean field that can be true or false, such that false comes first?

Now, at the code level, the answer to all 3 questions happens to be the same. But this is coincidental because it derives from different Java sorting rules. The fact that there's one answer that covers these 3 cases must not be interpreted to mean any ways of representing true and false will follow the same sorting logic.

At any rate, that answer is:

#set( $sortedList = $sorter.sort($originalList, "fieldName:asc") )

Because ascending (:asc) is the default order in SortTool, this can be shortened to:

And the same code also works if IsActive uses either of the 2 "boolean-ish" String conversions instead of true Boolean.

But again (and I'm going to pound you over the head with this) it's a coincidence. If Marketo happened to use yet another way of String-ifying Booleans, you might need to sort descending ($sorter.sort($originalList, "fieldName:desc")) or use another sorting method entirely to get the falses to the top.

The challenge

Now, the fun part. Rather than jump right to Part II (where I go deep into the technical details) I'll keep that follow-up post as a draft for a week, until September 18, 2018.

From now 'til Sept. 18, if you can answer the below challenge correctly, I'll give you a major shout-out in the follow-up... and hey, I may even have some side gigs for you! (Leave your answers in the comments, and please only try once per person.)

The background:

If Marketo used the Strings "-1" for false and "+1" for true then you'd need to use :desc instead of :asc. (And there's no standard that says they couldn't String-ify Booleans this way.) And same if they'd used symbols "⊥" and "⊤" , which appropriately denote truth and falsity (verum and falsum) in logic. Ditto if they'd used the Unicode characters for thumbs-up and thumbs-down.*

The question:

Why, in these 3 alternate cases, do you need :desc to get the originally false values to come first?

Be precise... no credit for "Because that's what Velocity requires." ☺

*Would've displayed the characters here, but the Jive editor strips them

out! You can see them on the original version of this post on my blog.