Guide Runtime
What & Why
Needing to use custom code to achieve that specialized experience desired for a specific guide, or just to accommodate an awkward App that has special requirements, is something that will always be necessary. Today, the custom code feature has minimal support behind it with really only the objects for the active guide and step being provided.
The goal with the Guide Runtime is to provide an enhanced toolbox for doing the things that are already being done; but now, they'll hopefully be done more easily, with less boilerplate code, and more safely as the Runtime will guarantee exception handling and improve ergonomics.
Guide Lifecycle
The Guide's Lifecycle includes all aspects of a Guide's existence from the time it is added to the DOM (also known as mounted). In addition to the normal events relevant for any UI added to a web page there are also Pendo Guide specific events. Vue lifecycle events were consulted as a reference for a starting point.
List of events:
beforeUpdate
position of guide is about to moveupdated
position of guide has movedbeforeUnmount
about to remove guide from dom (including property with reason for removal ie Advance, Hide, or Dismiss)unmounted
guide removed from dom (including property with reason for removal ie Advance, Hide, or Dismiss)beforeMount
called before guide has been displayed (only available through Global Scripts declaration)beforeAdvance
called before guide has been Advancedadvanced
guide was unmounted due to being AdvancedbeforeSnooze
called before guide has been Snoozedsnoozed
guide was unmounted due to be snoozedbeforeDismiss
called before guide has been Dismisseddismissed
guide was unmounted due to being DismissedbeforePrevious
called before guide has been "Advanced" to Previous Stepprevious
guide was unmounted due to be "Advanced" to Previous Stephidden
guide was unmounted due to being Hidden
JavaScript is running after it's included inside the Guide so it can't see the events preceding its existence like beforeCreate
and created
.
Documentation
Guide Runtime is made available as the context for the function in which all code block JS is run; it is accessible via the this
keyword.
Runtime Events:
on
Function that takes a String representing an event (listed below) and a callback function to be called when that event occurs. The update and unmount events (include the before* variety) are generic and called regardless of context as to why they occurred. The unmount events will include a reason field to provide that context. In addition, synthetic events for each reason will be generated to allow for less boilerplate coding when needing to only respond to specific scenarios in code.
Supported Events:
beforeUpdate
updated
beforeUnmount
unmounted
Synthetic events generated to simplify listening for a specific type or unmount
advanced
snoozed
dismissed
hidden
Event handlers will receive an Event argument. The Event argument will have a structure like:
- type - The type (from list above) of the event.
- reason - If type is unmounted or beforeUnmount then this will show what the UX reason for the unmount is or will be. Reasons are: advanced, snoozed, dismissed, or hidden.
- step - The step that is currently being evented upon.
Examples
The Runtime will be accessible inside the code function block by using the implicit this variable. A full description of the available API will be provided but for some potential examples a snippet might appear like:
this.on('beforeUnmount', evt => {
// this will fire before any event where the guide is removed
if (evt.unmountReason === 'advance') {
}
});
this.on('advanced', evt => {
// Do something here
});
this.on('dismissed', () => {});
// CodeBlock Function<GuideRuntime> - GuideRuntime is available in the this variable inside the function
this.on('advanced', () => {
alert('You are done!');
});
this.on('dismissed', () => {});
Global Scripts
Guide suites will often use the same code blocks over and over again to achieve a similar presentation across all guides. This is risky as it requires many guides to be edited manually and kept in sync.
To make this process easier and more predictable, the Pendo agent can be initialized with custom code blocks that run on every step. These are defined in a globalScripts list with the following format:
Some examples:
pendo.initialize({
visitor: { id: 12345 },
account: { id: 789 },
guides: {
globalScripts: [
{
script: function (step, guide) {
console.log('Step ', step.id, ' has run');
},
// Only run this on a specific known step id
test: function (step, guide) {
return step.id === 'lCQ_9DAgLJlfA6nibXYmUf-HucE';
}
},
{
script: function (step, guide) {
console.log('Step is a second step');
},
// Run on the second step of any guide
test: function (step, guide) {
return (
pendo._.findIndex(
guide.steps,
s => s.id === step.id
) === 1
);
}
},
{
script: function (step, guide) {
console.log('This guide has a badge');
},
// Run on all steps for guides that have a badge launch method
test: function (step, guide) {
return guide.launchMethod.includes('badge');
}
},
{
script: function (step, guide) {
console.log('This step matches the naming criteria');
},
// Run on all steps where the guide name or step name has "- Delay Script" in it
regex: /- Delay Script/
}
]
}
});
globalScripts
- an array of objects that each define a custom global scriptscript
- (required) custom function goes heretest
- (optional) test function gets the step and guide object as parameters and must return a boolean value that determines whether the script runs. Ex: only run this script on the first step of every guideregex
- (optional) regular expression compared against the step and guide name, running the script on any matches. Ex: a regex that matches common string among a suite of guides- For convenience, any supported event listed above may also be provided as a function. For example:
pendo.initialize({
guides: {
globalScripts: [
{
beforeMount: function () {
console.log('This runs before the guide displays');
},
script: function () {
console.log('This runs after the guide displays');
},
unmounted: function () {
console.log('This runs when the guide is hidden');
}
}
]
}
});
Global scripts will run after scripts defined in custom code blocks on the step itself via Designer
Guide Local Storage
Persisted data that is able to be accessed across repeated guide step views or even be available between steps in the same multi-step guide is already a technique being used but often doesn't safely do so as local storage can be tricky based on per user settings or browser capabilities.
This api will provide a way for the data to be safely stored and have its visibility scoped to ensure each guide can trust the values it requests.
Only accessible for the current Step
this.write('storageKey', 100); // takes key string and value and safely handles local storage
this.read('storageKey'); // returns value (100) from reading "storageKey"
this.clear('storageKey'); // clears value for "storageKey"
Accessible across Steps of the same Guide
this.writeByGuide('key', 200); // appends current guide ID to the key string and stores value
this.readByGuide('key'); // returns value (200) from reading "key" on the current guide
this.clearByGuide('key'); // clears value "key" for current guide
Canceling Events
An API for cancel is provided in the GuideRuntime that will prevent the next action, plus mark the event canceled for other listeners attached to the event. Available in 2.147.0 and later on the following event phases:
beforeAdvance
beforePrevious
beforeDismiss
beforeSnooze
To cancel during one of these phases, add a property "cancel" set to true on the event object passed into the runtime function.
// GuideRuntime is attached as 'this' in the Step's script
this.on('beforeAdvance', evt => {
if (!form.isValid()) {
evt.cancel = true;
}
});
The beforeMount
event is available in 2.234.0 or later. Due to technical limitations it cannot be subscribed with this.on()
from the script
function. It needs to be specified as a key in a globalScripts entry:
pendo.initialize({
guides: {
globalScripts: [
{
beforeMount: function (evt) {
console.log('This runs before the guide displays');
if (shouldCancel) {
evt.cancel = true; // set cancel to true to prevent the guide from displaying
}
}
}
]
}
});