Adobe Launch Dynamic Data Elements
Overview
One issue I faced at my job was that the data layer architecture was inconsistent across the various site sections. I’ll anonymize this by pretending I work for an airline; if we wanted to track the name of the destination(s) the user was looking at, there were a few main use cases:
The user is selecting (clicking) a specific destination, such as a filter in the booking flow, or a link to a content page about that destination.
The user is viewing search results for flights to a single or multiple destinations.
The user is looking at a content page about the destination itself.
A known user is just browsing around, but they already have a booking to a specific destination. (Knowing this helps us serve personalized content to them as they browse.)
Rather than having lots of diffent ‘destination’ data elements in our codebase, which then have to be mapped into different Launch rules and XDMs, we can create one ‘destination’ data element that checks for each of these use cases in order of priority.
Use Cases
Use Case 1: Modern Data Layer
The modern data layer architecture (which each of our site sections is refactoring to as each gets replatformed) groups the destination code
and name
together within each property, like this:
dataLayer: {
booking: {
destination: {
code,
name,
},
},
content: {
destination: {
code,
name,
},
},
search: {
destination: {
code,
name,
},
},
}
Use Case 2: Legacy Data Layer
Older site sections use a flatter data layer architecture, which does not nest the code
and name
together under one destination
property:
dataLayer: {
booking: {
destination,
destinationCode,
},
content: {
destination,
destinationCode,
},
search: {
destination,
destinationCode,
},
}
Use Case 3: Data Attributes (Clicks)
When we began implementing AEP as well, we created one global XDM that could be used for both clicks and page/route loads. The reasoning for this was twofold:
To be D.R.Y.
To ensure that the schema architecture is identical in all circumstances, preventing batch ingestion failures.
Therefore, these same dynamic data elements required logic that would first check if the rule-triggering event contained data attributes.
Syntax
Here is the actual programming. (Scroll down for a description of how it works.)
let destinationName;
let destinationNameArr;
let destinationNameMap;
const clickAttributes = (!!event && !!event.detail) ? event.detail : '';
if (
!!clickAttributes &&
!!clickAttributes['data-track-noun'] &&
clickAttributes['data-track-noun'] === 'filter-destination' &&
!!clickAttributes['data-track-name']
) {
// Booking Flow Destination Filter Clicks (future-proofed version):
destinationName = clickAttributes['data-track-name'];
} else if (
!!clickAttributes &&
!!clickAttributes['data-track-noun'] &&
clickAttributes['data-track-noun'] === 'search-filter'
) {
// Do nothing.
} else if (!!window.dataLayer) {
if (
!!window.dataLayer.booking &&
typeof window.dataLayer.booking.destination === 'object' &&
typeof window.dataLayer.booking.destination.name === 'string'
) {
destinationName = window.dataLayer.booking.destination.name;
} else if (
!!window.dataLayer.booking &&
typeof window.dataLayer.booking.destination === 'string'
) {
destinationName = window.dataLayer.booking.destination;
} else if (
!!window.dataLayer.search &&
!!window.dataLayer.search.destination &&
Array.isArray(window.dataLayer.search.destination)
) {
destinationNameArr = window.dataLayer.search.destination;
destinationNameMap = destinationNameArr.map(destination => destination.name);
if (!!destinationNameMap && Array.isArray(destinationNameMap)) {
destinationName = destinationNameMap.toString();
}
} else if (
!!window.dataLayer.search &&
typeof window.dataLayer.search.destination === 'string'
) {
destinationName = window.dataLayer.search.destination;
} else if (
!!window.dataLayer.content &&
typeof window.dataLayer.content.destination === 'object' &&
typeof window.dataLayer.content.destination.name === 'string'
) {
destinationName = window.dataLayer.content.destination.name;
} else if (
!!window.dataLayer.content &&
typeof window.dataLayer.content.destination === 'string'
) {
destinationName = window.dataLayer.content.destination;
}
}
if (
!!destinationName &&
typeof destinationName === 'string' &&
destinationName !== 'NA'
) {
return destinationName;
}
Explanation
Line 1: Declare the variable where the value will be stored. For AEP purposes, do not set any default value. (If there is no value, we want this data element to return
undefined
so that its property is omitted from the XDM. If it defaults to''
ornull
, the property will be added to the XDM, which could result in a batch ingestion failure.)Lines 2 - 3: Declare the variables that will be used to handle array use cases later in this flow.
Line 5: If the rule that invokes this data element has been triggered by a click, an object will have been passed into it. That object will be the clicked element. We store it in the
clickAttributes
variable. (And if the event was not a click, we default this variable to an empty string so that its condition will not be met later in our logic.)Lines 7 - 14: The first thing we check are the click use cases. Some of our pages follow the new and improved convention of having
data-track-noun="filter-destination"
attributes. If a clicked element has this attribute, we’ll want to pull the value from itsdata-track-name
attribute. We check that both exist before attempting to do this.Lines 15 - 20: Some of our pages have search filters whose data attributes have not been updated to the new and improved convention described above. If this is the case, they will have a legacy attribute called
data-track-noun="search-filter"
. If so, they do not also contain adata-track-name
attribute, so we can’t pull the destination name from it. Instead, we keep a condition here that will be met but do nothing. This prevents clicks on these legacy elements from falling back to the values in the data layer further down the flow — which would not accurately reflect what was clicked.Lines 21 - 60: We now enter the data layer conditions.
Lines 22 - 32: First we check if a
booking
destination exists.Lines 22 - 27: We first look for the
booking
property in the modern data layer architecture.Line 24: Rather than just checking to see if
window.dataLayer.booking.destination
exists, we confirm that it is an object. This is because if our code was reading the legacy data layer, this condition would be met even though it is only a string. If that were the case, attempting to checkwindow.dataLayer.booking.destination.name
on the next line would throw an error.Lines 25 - 27: Having confirmed that
window.dataLayer.booking.destination
is an object, we now confirm thatwindow.dataLayer.booking.destination.name
is a string. If so, we store it in ourdestinationName
variable.
Lines 28 - 32: If the destination name was not found in the modern data layer architecture, we check the legacy data layer architecture next.
Lines 33 - 47: If the destination name was not found in the
booking
properties, it’s time to check thesearch
properties — first in the modern architecture, then in the legacy architecture.Lines 33 - 36: The modern data layer’s
search
property is a little more complex because it is an array. The user has the ability to search for multiple destinations at a time. This first condition includes validation to confirm that we are indeed dealing with an array before we try to run methods on a property that will throw an error if it’s not an array.Line 38: Store the array in
destinationNameArr
. (Each item in the array will contain both a destinationname
and a destinationcode
.)Line 39: Map each
name
value into a new array, and store it indestinationNameMap
.Lines 40 -42: We do another quick validation check to make sure we aren’t pulling the wrong data type, and then we convert the array into a string.
Lines 44 - 47: On the other hand, if we are working with the legacy data layer, the list of destination names will already be a string. And they will not include destination codes.
Lines 48 - 59: If the destination name was not found in
booking
orsearch
, next checkcontent
. As always, we check for the modern data layer first, and the legacy data layer second.Lines 50 - 51: It is important to confirm that
dataLayer.content.destination
is an object before considering it to be a match. This is because the legacy data layer also contains this exact same property, only it is a string and contains the final value. If this is the case and we next try to run some dot notation on it (on line 51), it will throw an error.
Lines 63 - 65: To prevent batch ingestion failures in AEP, we make sure no
null
values, empty strings, or'NA'
default values from the data layer get passed through. Done!