Skip navigation
All Places > Products > Blog > 2018 > September
2018

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).

 

Here's the briefest possible copypasta to add to that LP:

 

<style type="text/css">
.webinar-pending-note { 
  display: none; 
}
[data-url-ready-state="pending"] .webinar-pending-note { 
  display: block; 
}
</style>
<script>
var memberWebinarURL = "{{Member.Webinar URL}}";
if ( memberWebinarURL ) {
  document.location.href = memberWebinarURL;
  // just for completeness
  document.documentElement.setAttribute("data-url-ready-state","ready"); 
} else {
  document.documentElement.setAttribute("data-url-ready-state","pending");
}
</script>
<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 Agical Add 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:

ss

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.

 

That is, instead of:

 

#set( $baseURL = "https://www.example.com/preferencecenter" )
#set( $linkText = "Say \u0022Hello\u0022 to our new preference center." )

 

I give them a token like so:

 

#define( $baseURL )
https://www.example.com/preferencecenter
#end
#define( $linkText )
Say "Hello" to our new preference center.
#end

 

As long as they leave the #define/#end lines alone they can change anything in-between (especially good for multiline text, as you might imagine).

 

There's a little trick to using #define, though, and that is like everything in Velocity, it preserves whitespace.  What whitespace, you may ask? Well, look at the end of this line:

 

https://www.example.com/preferencecenter

 

That has a carriage return + line feed (or just LF, depending on the OS) at the end.

 

So if I output a link like so:

 

<a href="${baseURL}">${linkText}</a>

 

The email will contain:

 

<a href="https://www.example.com/preferencecenter
">Say "Hello" to our new preference center.
</a>

 

Instead of what you intended:

 

<a href="https://www.example.com/preferencecenter">Say "Hello" to our new preference center.</a>

 

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:

 

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

 

And you're done!

 

 


 

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? ☺

Will Harmon

Adobe to Acquire Marketo

Posted by Will Harmon Employee Sep 20, 2018

As you may have heard, earlier today Adobe announced they have entered into a definitive agreement to acquire Marketo to widen their leadership in customer experience. Marketo CEO Steve Lucas published a blog that highlights both Marketo's and Adobe's joint vision to empower marketers to deliver exceptional end-to-end customer experiences:

 

"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.

 

Blog from Marketo CEO Steve Lucas: Adobe to Acquire Marketo to Place Customer Experience and Engagement at the Heart of Digital Transformation
Adobe Press Release: Adobe Expands Customer Experience Leadership with Addition of Marketo | Adobe Blog

Quick Velocity learner EC asked a seemingly simple question:

 

How do you sort Velocity objects based on a Boolean field, such that false values come first?

 

What makes it not-so-simple is that it's actually 3 questions in one!

 

Realize that something labeled as Boolean in Marketo (in the Field Management or Custom Objects admin sections) may or may not end up as a Boolean field in Velocity.

 

Booleans on Leads

As I've delved into before, regardless of the data type set in the database, Lead/Person fields in Marketo become String properties of the $lead object in Velocity:

  • Dates and DateTimes are not dates in Velocity, but merely date-like Strings (that's why you need to go through all that pain to convert them).
  • 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:

 

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

 

So if you have these 3 objects in a list $originalList:

 

[{
  "Name" : "A",
  "IsActive" : false,
  "ExternalCreatedDate" : "2017-07-01"
},
{
  "Name" : "B",
  "IsActive" : true,
  "ExternalCreatedDate" : "2017-08-01"
},
{
  "Name" : "C",
  "IsActive" : false,
  "ExternalCreatedDate" : "2017-09-01"
}]

 

And you do:

 

#set( $sortedList = $sorter.sort($originalList, "IsActive") )

 

Then $sortedList will be:

 

[{
  "Name" : "A",
  "IsActive" : false,
  "ExternalCreatedDate" : "2017-07-01"
},
{
  "Name" : "C",
  "IsActive" : false,
  "ExternalCreatedDate" : "2017-09-01"
},
{
  "Name" : "B",
  "IsActive" : true,
  "ExternalCreatedDate" : "2017-08-01"
}]

 

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.

Filter Blog

By date: By tag: