Skip to content

Instantly share code, notes, and snippets.

@sahava
Last active August 13, 2024 08:32
Show Gist options
  • 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

@jati888
Copy link

jati888 commented Aug 13, 2024

Hi everyone,

Is there a possibility that if the website uses a pop-up form, the persistent data layer will not work? Because I can see the value of the variable before the page reloads. Once it reloads, the value is no longer there.

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