Skip to content

Instantly share code, notes, and snippets.

@sahava
Last active March 13, 2024 12:26
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save sahava/baee63650eed471a2d1eac5825037a05 to your computer and use it in GitHub Desktop.
Save sahava/baee63650eed471a2d1eac5825037a05 to your computer and use it in GitHub Desktop.
JavaScript for persisting dataLayer array and data model composition across pages
(function() {
// Set the timeout for when the dataLayer history should be purged. The default is 30 minutes.
// The timeout needs to be in milliseconds.
var timeout = 30*60*1000;
// Change dataLayerName only if you've defined another named for the dataLayer array in your
// GTM container snippet.
var dataLayerName = 'dataLayer';
// Don't change anything below.
// Initial settings
var oldPush = window[dataLayerName].push,
containerId = {{Container ID}};
// Method to copy items from dataLayer from before the GTM container snippet was loaded.
var backfillHistory = function() {
var tempHistory = [],
i = 0,
len = window[dataLayerName].length;
for (; i < len; i++) {
tempHistory.push(window[dataLayerName][i]);
}
return tempHistory;
};
// Method to check if object is a plain object.
// From https://bit.ly/2A3Fuqe
var isPlainObject = function(value) {
if (!value || typeof value !== 'object' || // Nulls, dates, etc.
value.nodeType || // DOM nodes.
value === value.window) { // Window objects.
return false;
}
try {
if (value.constructor && !value.hasOwnProperty('constructor') &&
!value.constructor.prototype.hasOwnProperty('isPrototypeOf')) {
return false;
}
} catch (e) {
return false;
}
var key;
for (key in value) {}
return key === undefined || value.hasOwnProperty(key);
};
// Method to merge the stored data model and the history model together.
// From https://bit.ly/2FrPQWL
var mergeStates = function(storedModel, historyModel) {
for (var property in storedModel) {
if (storedModel.hasOwnProperty(property)) {
var storedProperty = storedModel[property];
if (Array.isArray(storedProperty)) {
if (!Array.isArray(historyModel[property])) historyModel[property] = [];
mergeStates(storedProperty, historyModel[property]);
} else if (isPlainObject(storedProperty)) {
if (!isPlainObject(historyModel[property])) historyModel[property] = {};
mergeStates(storedProperty, historyModel[property]);
} else {
historyModel[property] = storedProperty;
}
}
}
};
var getODataModel = function() {
return window.google_tag_manager[containerId].dataLayer.get({split: function() { return []; }});
};
// **Initialize upon first load**
// Build the history array from local storage
var dHistory = window._dataLayerHistory = JSON.parse(
window.localStorage.getItem('_dataLayerHistory') || '{}'
);
// Method to reset the history array to the current page state only
dHistory.reset = function() {
dHistory.timeout = new Date().getTime() + timeout;
dHistory.history = backfillHistory();
for (var prop in dHistory.model) {
if (dHistory.model.hasOwnProperty(prop) && prop !== 'get') {
delete dHistory.model[prop];
}
}
mergeStates(getODataModel(), dHistory.model);
window.localStorage.setItem('_dataLayerHistory', JSON.stringify(dHistory));
};
// If initial load
if (!dHistory.timeout) {
dHistory.timeout = new Date().getTime() + timeout;
dHistory.history = [];
dHistory.model = {};
}
dHistory.history = dHistory.history.concat(backfillHistory());
mergeStates(getODataModel(), dHistory.model);
// If timeout is reached, reset the history array
if (dHistory.hasOwnProperty('timeout') && dHistory.timeout < (new Date().getTime())) {
dHistory.reset();
}
// From https://bit.ly/2A2ZcCG
dHistory.model.get = function(key) {
var target = dHistory.model;
var split = key.split('.');
for (var i = 0; i < split.length; i++) {
if (target[split[i]] === undefined) return undefined;
target = target[split[i]];
}
return target;
};
// Write the new history into localStorage
window.localStorage.setItem('_dataLayerHistory', JSON.stringify(dHistory));
window[dataLayerName].push = function() {
try {
// Initial settings
var states = [].slice.call(arguments, 0),
timeNow = new Date().getTime(),
results = oldPush.apply(window[dataLayerName], states),
oDataLayer = window[dataLayerName],
oDataModel = getODataModel();
// If timeout is reached, reset the history array
if (dHistory.hasOwnProperty('timeout') && dHistory.timeout < (new Date().getTime())) {
dHistory.reset();
}
// Push latest item from dataLayer into the history array
dHistory.history.push(oDataLayer[oDataLayer.length-1]);
// Merge GTM's data model with the history model
mergeStates(oDataModel, dHistory.model);
// Update the timeout
dHistory.timeout = timeNow + timeout;
// Write the new history into localStorage
window.localStorage.setItem('_dataLayerHistory', JSON.stringify(dHistory));
return results;
} catch(e) {
console.log('Problem interacting with dataLayer history: ' + e);
var states = [].slice.call(arguments, 0),
results = oldPush.apply(window[dataLayerName], states);
return results;
}
};
})();
@RafaelRochaweb
Copy link

Thank you very much, but how to use it?

@pedrotalaia
Copy link

Thank you very much, but how to use it?

There is an article explaining this step-by-step here: https://www.simoahava.com/analytics/persist-datalayer-across-pages/

@julianwitzel
Copy link

I am not able to retrieve data of a previous page if I push the data to the dataLayer via form submit event.

This is what I am getting:
VM585:4 Problem interacting with dataLayer history: TypeError: Converting circular structure to JSON
--> starting at object with constructor 'HTMLFormElement'
| property 'jQuery351089137954889473742' -> object with constructor 'Object'
| property '.wForm' -> object with constructor 'Object'
| property 'form' -> object with constructor 'S.fn.init'
--- property '0' closes the circle

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment