Sanford Whiteman
a.k.a. “that guy” from the Marketo Community
who also blogs at https://blog.teknkl.com
and has created some Marketo apps
such as FlowBoost.
A TEKNKL::BLOG TEASER
(NOT FOR TODAY!)*
*Unless we have spare time
h/t NEAL FORD's
“SUCK/ROCK DICHOTOMY”
Sandy'S
“CONTINUOUS SUCKROCKING” METHODOLOGY
NAME THAT SMART CAMPAIGN!
Solve
for
?????????????????
Fixing
known visitor html
-
KV HTML = “If Known Visitor, Show Custom HTML”
-
KV HTML is a mini-form (with Download <button> or links if you want) instead of standard form layout
-
will not kick in if the lead's First Name or Last Name is empty (!!!)
-
but will kick in if the lead's Email Address is empty (think about it)
ALTERNATIVE FACT:
ACTUAL FACTUAL:
- You need a strategy for dealing with empty names
- If you want people to be able to correct an invalid Email Address, consider proxy fields and blanking out First and/or Last Name
- Proxy fields are a good investment anyway (only way to let someone change their email via Marketo form!)
The OTHER
KV HTML BUG
- KV HTML is still a form and will result in a Filled Out Form activity
- But native Auto-Fill for hidden fields is disabled (!!!)
- UTMs (and other query params) will not be tracked
- much confusion ensues when all revisits appear to be Direct
the suckitude
the rockness
- You need to (re)build query param, cookie, and referrer stuff in JS
- Be nice if this were documented
- You get to (re)build query param, cookie, and referrer stuff in JS... and make it better!
- The Forms JS API makes it easy to hook into addHiddenFields()
- Replace all your UTM tracking with smarter JS
var hiddenFieldMap = {
Last_Result__c: {
/* this field is filled from the current query parameter `utm_results` */
channel: "currentQuery",
selector: "utm_results"
},
Field_From_Cookie: {
/* this one is filled from the full value of the saved cookie `ahoy_visitor` */
channel: "cookie",
selector: "ahoy_visitor",
},
Field_From_Cookie_As_Object: {
/* this one is filled from a *single property* of a cookie `utm_history_1`
that was stored as a JSON object */
channel: "cookie",
selector: "utm_history_1",
property: "utm_zz"
},
Another_Field_From_Constant: {
/* this one is always set to the same value whenever somebody hits this page*/
channel: "constant",
selector: "Always use this value."
},
Field_From_Query_First_Then_Cookie: [
/* this one first looks in the current query param `utm_campaign` and if
that value was not found, moves on to the saved cookie `last_utm_campaign` */
{
channel: "currentQuery",
selector: "utm_campaign"
},
{
channel: "cookie",
selector: "last_utm_campaign"
}
],
Field_From_Referrer_URL: {
/* this one uses the query param `search_results` from the document.referrer URL */
channel: "referrerQuery",
selector: "search_results"
}
};
/* --- NO NEED TO EDIT BELOW THIS LINE! --- */
var currentCookies = FormsPlus.util.Cookies.getJSON(),
currentQuery = FormsPlus.util.URI.URI().search(true),
referrerQuery = FormsPlus.util.URI.URI(document.referrer||"").search(true);
var hiddenFields = Object.keys(hiddenFieldMap)
.reduce(function(acc, field) {
var fieldDescriptor = hiddenFieldMap[field],
allDescriptors = [].concat(fieldDescriptor);
allDescriptors
.reverse()
.forEach(function(descriptor) {
switch (descriptor.channel) {
case "cookie":
if(currentCookies[descriptor.selector] && descriptor.property) {
/* ... and so on... */
better auto-Fill JS
ThE
LEAD LOOKUP FORM
PATTERN
leads hit your subscription center
without clicking a tracked link
or submitting a form (in that browser).
What do you show 'em?
This ISn't the same as PRE-FILL!
Pre-Filling known lead data from
Marketo can work on any form
(or even a non-Marketo one)...
... but always depends on
the lead's web session or pageview
being associated with their lead
when the form renders.
-
might “unsubscribe all” if don't see they're already subbed
-
same for any lead fields and especially opt-in fields
-
better: retrieve their current values for update
Lead lookup Form: GENERAL DESIGN
After standard Marketo form onSuccess(),
start checking contents of hidden IFRAME
Poll IFRAME every N seconds
(up to max retries) until associated
Return data to parent window
on email match and run JS API setValues()
Lead lookup Form JS
form.onSuccess(function(vals, originalThankYouURL) {
var thankYouURL = "/Lead-Lookup-Frame-v1.0.0",
thankYouIframe = document.createElement("iframe"),
totalAttempts = 10,
delayInterval = 1000;
thankYouIframe.src = thankYouURL;
thankYouIframe.style.display = "none";
thankYouIframe.addEventListener("load", function(e) {
if (window.mktoAssociatedLead.Email != vals.Email) {
if (--totalAttempts) {
setTimeout(function() {
thankYouIframe.contentWindow.document.location.reload();
}, delayInterval);
}
} else {
form.setValues({
Active_Subscriptions_Indicator__c : mktoAssociatedLead.memberOfLists
});
emailEl.disabled = true; // lock the current Email
buttonEl.disabled = false; // reactivate the button so lead can update
buttonEl.textContent = "Update Subscriptions";
form.onSuccess();
}
});
/* ... and so on... */
OKAY, BUT... what about malice?
I agree, great bad movie.
Ah, right.
the
“self-service auth code”
enhancement
self-service auth code =
Marketo unique code
Lead lookup Form w/auth
After standard Marketo form onSuccess(),
check contents of IFRAMEd pURL
pURL responds immediately (no polling needed)
Return data to parent window
and run JS API setValues()
form.onSuccess(function(vals, originalThankYouURL) {
var thankYouURL = "/Lead-Lookup-Frame-pURL-v1.0.0/" + vals.selfServiceFormAuthCode,
thankYouIframe = document.createElement("iframe"),
totalAttempts = 1,
delayInterval = 1000;
thankYouIframe.src = thankYouURL;
thankYouIframe.style.display = "none";
thankYouIframe.addEventListener("load", function(e) {
if (window.mktoAssociatedLead && mktoAssociatedLead.Email != vals.Email) {
alert("Sorry, we could not find a matching lead record." );
buttonEl.disabled = false;
buttonEl.textContent = "Lookup Subscriptions";
} else if (window.mktoAssociatedLead) {
form.setValues({
Active_Subscriptions_Indicator__c : mktoAssociatedLead.memberOfLists
});
[emailEl,authCodeEl].forEach(function(el){
el.disabled = true; // disable Email and Auth Code inputs
});
buttonEl.disabled = false;
buttonEl.textContent = "Update Subscriptions";
form.onSuccess();
}
});
/* ... and so on... */
AUTH'D lookup Form JS
Yes, there's a sort-of-catch still.
(Wanna guess?)
ThE
RESOURCE LEAD
PATTERN
RESOURCE LEAD
- concept taken from “groupware” apps (Exchange, Notes, etc.)
- in groupware, a non-human object stored in a company directory alongside people objects
RESOURCE LEAD
- you book a resource and it keeps track of who is (and who has been) using it
- in Marketo, similar notion applied to Programs
RESOURCE LEAD
- you book a resource and it keeps track of who is (and who has been) using it
- in Marketo, concept can be applied to programs as resources
- allows you to count members of an event program as a Score field on a resource lead
(olde tyme
record scratch photo
because all the other
stock photos were
about “scratching”
as in DJing skillz)
- not the easiest way to do things like event caps (that would be FlowBoost or, heh, comparable services)
- but it's the only way that uses Marketo alone (think compliance, DPAs, etc.)
- and it's a deeply strange & cool envelope-pusher with Marketo technology
- if you get all of it, you can retire at the top of your game :)
RESOURCE LEAD:
The innocent beginning
RESOURCE LEAD:
The innocent beginning
RESOURCE LEAD:
BIND the RESOURCE TO THE PRogram
RESOURCE LEAD:
Now it gets crazy
- we need to update fields on a program's resource lead based on activity in the program
- we need to read the resource lead's fields from a browser, without being Munchkin-associated as the resource lead (and without being associated at all)
RESOURCE LEAD:
reading resource lead data
- wouldn't it be great if there were a browser-ready web service built into Marketo?
- is XML browser-ready enough for ya?
- can you make a Marketo Landing Page that's valid XML?
RESOURCE LEAD:
reading resource lead data
- yes, a Marketo Landing Page can be valid XML (not just HTML)!
- uses a mktoString variable trick
<!DOCTYPE html>
<html>
<meta class="mktoString" allowHtml="true" id="commentOpen" mktoName="commentOpen">
<meta class="mktoString" allowHtml="true" id="commentClose" mktoName="commentClose">
${commentOpen}
<head>
<title></title>
<meta charset="utf-8">
</head>
${commentClose}
<body>
<lead>
<attributes>
<score>{{lead.Lead Score}}</score>
</attributes>
</lead>
</body>
</html>
XML-IFIED MkTo TEMPLATE + LP
Sample xml response
<!DOCTYPE html>
<html>
<!--
<head>
<title></title>
<meta charset="utf-8"><meta name="robots" content="index, nofollow">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" >
<link rel="icon" href="/favicon.ico" type="image/x-icon" >
<style>.mktoGen.mktoImg {display:inline-block; line-height:0;}</style>
</head>
-->
<body id="bodyId">
<lead>
<attributes>
<score>126</score>
</attributes>
</lead>
</body>
</html>
RESOURCE LEAD:
reading resource lead data
- now you have an XML service that you can read from the browser (via Ajax) and from the server (via webhook)
var getResourceLead = new XMLHttpRequest;
getResourceLead.open("GET","/XHTML-Lead-Data-Source/Programid4431Resourcelead");
getResourceLead.responseType = "document";
getResourceLead.onabort = getResourceLead.onerror = restoreCookie;
getResourceLead.onload = function(){
var counterFromResourceLead = this.responseXML.querySelector("score");
MktoForms2.whenReady(function(form){
var formEl = form.getFormElem()[0],
formCounterOutput = formEl.querySelector("#currentEventCount");
formCounterOutput.textContent = counterFromResourceLead.textContent;
});
};
getResourceLead.send();
JS TO get XML counter INTO PAGE
var getResourceLead = new XMLHttpRequest;
getResourceLead.open("GET","/XHTML-Lead-Data-Source/Programid4431Resourcelead");
getResourceLead.responseType = "document";
getResourceLead.onabort = getResourceLead.onerror = restoreCookie;
getResourceLead.onload = function(){
var counterFromResourceLead = this.responseXML.querySelector("score");
restoreCookie();
MktoForms2.whenReady(function(form){
var formEl = form.getFormElem()[0],
formCounterOutput = formEl.querySelector("#currentEventCount");
formCounterOutput.textContent = counterFromResourceLead.textContent;
});
};
var restoreCookie = function(){
if (originalEndLeadCookieValue) {
FormsPlus.util.Cookies.set(
cookieName,
originalEndLeadCookieValue,
cookieOptions
);
}
};
FormsPlus.util.Cookies.remove(
cookieName,
cookieOptions
);
getResourceLead.send();
(OK, JS IS more like this)
Or you can redirect to an “Event Full” page if over your max, or anything else you want to do with the info...
if (countFromResourceLead > {{my.Registrant Limit}})
document.location.href = "/event-is-full.html";
You can just display the current count in the page...
On the server side, you can also bring the Resource Lead's data onto regular leads using a webhook...
RESOURCE LEAD:
WrITING resource lead data
- semi-secret /save endpoint can be POSTEd from a webhook
- this allows cross-lead updates (program member lead ⇨ resource lead)
This webhook notifies the Resource Lead that there's action in the program...
As
human
leads
register...
... the
Resource
Lead's
score
is bumped!
RESOURCE LEAD:
The circle is complete(-ly crazY)
- more than just program counts and event caps
- you can also use the Resource Lead to schedule global outbound connections to other sources (think RSS)
- you can subscribe people to a Smart List of all Resource Leads to see totals, or Send Alerts containing Resource Lead aggregate data
DONE FOR TODAY!
Remember: go to https://blog.teknkl.com
DONE FOR TODAY!
Marketo MUG 2019-01 Recorded
By Sanford Whiteman, TEKNKL