Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
657 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# GTM-Storage | ||
|
||
Idea of this solution is based on [HTML5 Local Storage](https://www.w3schools.com/html/html5_webstorage.asp). I did not | ||
want to send data to [Google Tag Manager](http://www.google.pl/tagmanager/) immediately after the event is triggered on | ||
my website. | ||
|
||
* Demo: [http://example.silversite.pl/gtm/storage/](http://example.silversite.pl/gtm/storage/) | ||
|
||
### Problems with regular solution | ||
|
||
On the [Developer Guide](https://developers.google.com/tag-manager/devguide#events) you can see the sample of usage: | ||
|
||
```html | ||
<a href="page.html" onclick="dataLayer.push({'event': 'button-click'});">My button</a> | ||
``` | ||
|
||
where method `dataLayer.push()` starts immediately after clicking on the link. And here you can have the problem with | ||
sending data to [GTM](http://www.google.pl/tagmanager/) because **you have to trust that sending will be faster than | ||
page reloading** to _page.html_. You can use also | ||
[event callback datalayer variable](https://developers.google.com/tag-manager/enhanced-ecommerce#product-clicks) but | ||
cache (GTM-Storage) is better... | ||
|
||
### GTM-Storage solution | ||
|
||
GTM-Storage **is independent** of [GTM](http://www.google.pl/tagmanager/) and `dataLayer` object. It just creates | ||
numerous elements which the event was made on. In your code you can use storaged data to recursive reading it and | ||
sending to [GTM](http://www.google.pl/tagmanager/) in given interval time. | ||
|
||
Saving data to storage and reading it **is safe** because no javascript error will stop the default website event | ||
like page reloading based on | ||
[callback event](https://developers.google.com/tag-manager/enhanced-ecommerce#product-clicks). | ||
|
||
## Quick start | ||
|
||
Copy the following link to the main GTM-Storage file and paste it to the `<head>` tag on every page of your website: | ||
|
||
```html | ||
<script src="gtmstorage.js"></script> | ||
``` | ||
|
||
Put the following link to the script at the [bottom](https://developer.yahoo.com/performance/rules.html#js_bottom) of | ||
your markup right after [jQuery](https://jquery.com/): | ||
|
||
```html | ||
<script src="jquery.js"></script> | ||
<script src="script.js"></script> | ||
``` | ||
|
||
## Usage | ||
|
||
Use [HTML event attributes](https://www.w3schools.com/tags/ref_eventattributes.asp) to set the event in the HTML tag | ||
and call the `gtmStorage.push()` method with `this` argument (HTML DOM element). | ||
|
||
Use HTML tag attribute `data-gtm` to set data to send to [GTM](http://www.google.pl/tagmanager/). The attribute | ||
`data-gtm` needs to get value in JSON format with one required property: | ||
* `event` - custom event name [required] | ||
* `data` - custom data with any format (object, string, number, bool) | ||
|
||
Here is an example: in order to set an event when a user clicks a link, you might modify the link to call the `push()` | ||
and enter data by `data-gtm` as follows: | ||
|
||
```html | ||
<a href="#url" | ||
onclick="gtmStorage.push(this)" | ||
data-gtm='{"event":"customEventName", "data":{"any":"data","you":"need"}}'>link anchor</a> | ||
``` | ||
|
||
Feel free to modify _script.js_ file and create any solution you need. For example conditional statements on handling | ||
Ecommerce object format like | ||
[Measuring Product Clicks](https://developers.google.com/tag-manager/enhanced-ecommerce#product-clicks) with required | ||
`ecommerce` property. | ||
|
||
### Why DOM Level 0 event model? | ||
|
||
In the previous example I used [DOM Level 0](https://www.w3.org/TR/uievents/#dom-level-0) event model. It means | ||
triggering of event in HTML tag property, eg. `<span onclick="gtmStorage.push(this)" />`. | ||
|
||
I have chosen this event model because **it works faster**. For example, if you want to use | ||
[DOM Level 2](https://www.w3.org/TR/DOM-Level-2-Events/) event model based on [jQuery](https://jquery.com/), event | ||
listeners start working after DOM is ready. And if your website has 100K lines of code, it is possible that the user | ||
will start using the website before it is ready, and you lose some events and stats. If you paste the _gtmstorage.js_ | ||
file in `<head>` and you use [DOM Level 0](https://www.w3.org/TR/uievents/#dom-level-0) event model - you do not lose | ||
the events. | ||
|
||
This solution is ready also in content loaded by AJAX. | ||
|
||
## License | ||
|
||
Code released under the MIT license. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
/** | ||
* GTM-Storage | ||
* | ||
* Using HTML5 Local Storage creates numerous stack elements which the event was made on. | ||
* | ||
* @version 0.0.1 | ||
* @link https://github.com/cichy380/GTM-Storage | ||
* @author Marcin Dobroszek | ||
* @license The MIT License (MIT) | ||
* | ||
* @todo action after lock storage handling | ||
*/ | ||
var gtmStorage = (function() { | ||
'use strict'; | ||
|
||
var namespace = 'gtm', | ||
|
||
/** | ||
* Returns all data about storaged events. | ||
* @return {object} data - List of events | ||
*/ | ||
getItems = function () { | ||
var localStorageValue = typeof localStorage.getItem(namespace) !== 'undefined' | ||
&& localStorage.getItem(namespace) ? localStorage.getItem(namespace) : '[]'; | ||
|
||
return JSON.parse(localStorageValue); | ||
}, | ||
|
||
/** | ||
* Saves new data in storage. | ||
* @param {array} data - List of events | ||
*/ | ||
_saveGtmStorage = function (data) { | ||
// if GTM Storage is not lock we can save new data (add new event) .. | ||
if (localStorage.getItem(namespace + 'lock') === 'off') { | ||
|
||
// set lock on GTM Storage | ||
localStorage.setItem(namespace + 'lock', 'on'); | ||
|
||
localStorage.setItem(namespace, JSON.stringify(data)); | ||
|
||
// set unlock on GTM Storage | ||
localStorage.setItem(namespace + 'lock', 'off'); | ||
} else { | ||
// .. else lost info about new data (new events) | ||
// TODO: lock storage handling | ||
} | ||
}, | ||
|
||
/** | ||
* Changes item data in storage. | ||
* @param {array} data - New data of item event | ||
* @param {int} id - ID of item event to edit | ||
*/ | ||
editItem = function (data, id) { | ||
var currentGtmStorage, | ||
newGtmStorage; | ||
|
||
newGtmStorage = []; | ||
currentGtmStorage = getItems(); | ||
currentGtmStorage.forEach(function(item, index) { | ||
if (item.id === id) { | ||
newGtmStorage.push(data); | ||
} else { | ||
newGtmStorage.push(item); | ||
} | ||
}); | ||
|
||
_saveGtmStorage(newGtmStorage); | ||
}, | ||
|
||
removeItem = function (id) { | ||
var currentGtmStorage = getItems(), | ||
newGtmStorage = []; | ||
|
||
currentGtmStorage.forEach(function (item) { | ||
if (item.id !== id) { | ||
newGtmStorage.push(item); | ||
} | ||
}); | ||
|
||
_saveGtmStorage(newGtmStorage); | ||
}, | ||
|
||
/** | ||
* Clears flags "sending" from all data items and allows next sending try. | ||
*/ | ||
_clearSendingFlag = function () { | ||
var gtmStorage = getItems(); | ||
|
||
gtmStorage.forEach(function (item, index) { | ||
if (item.sending === true) { // not sending yet | ||
// prevent double sending | ||
item.sending = false; | ||
editItem(item, item.id); | ||
} | ||
}); | ||
}, | ||
|
||
/** | ||
* Adds element to storage. | ||
* @param {HTML DOM element} element | ||
*/ | ||
push = function (element) { | ||
var gtmLocalStorage; | ||
|
||
if (typeof localStorage !== 'object' || typeof JSON !== 'object') { | ||
// browser does not support required function | ||
return; | ||
} | ||
|
||
try { | ||
// reads current data and convert to array | ||
gtmLocalStorage = JSON.parse(localStorage.getItem(namespace) || '[]'); | ||
} catch (errMsg) { | ||
// problem with data in localStorage -- clear all data | ||
localStorage.removeItem(namespace); | ||
gtmLocalStorage = []; | ||
|
||
window.console && console.error(errMsg); | ||
} | ||
|
||
// push new data (new element) | ||
gtmLocalStorage.push({ | ||
id: Math.random(), | ||
time: new Date(), | ||
element: element.outerHTML, | ||
sending: false, // FALSE == item did not send yet, TRUE == just sending | ||
}); | ||
|
||
// save | ||
_saveGtmStorage(gtmLocalStorage); | ||
}, | ||
|
||
/** | ||
* Initialization of GTM-Storage. | ||
*/ | ||
_init = function () { | ||
if (typeof localStorage === 'object') { | ||
// reset always after start loading website (this file loaded) | ||
localStorage.setItem(namespace + 'lock', 'off'); | ||
|
||
_clearSendingFlag(); | ||
} | ||
}, | ||
|
||
/** | ||
* Returns easy readable descrition of HTML element. | ||
* Eg. "<a.btn.btn-submit>" | ||
* @param {HTML DOM element} element | ||
* @return {string} | ||
*/ | ||
getElementName = function (element) { | ||
var tagName = element.localName, | ||
idName = element.id ? '#' + element.id : '', | ||
classNameListString = element.classList.length | ||
? '.' + Array.prototype.join.call(element.classList, '.') : ''; | ||
|
||
return '<' + tagName + idName + classNameListString + '>'; | ||
}; | ||
|
||
// initialization | ||
_init(); | ||
|
||
return { | ||
namespace: namespace, | ||
push: push, | ||
getItems: getItems, | ||
editItem: editItem, | ||
removeItem: removeItem, | ||
getElementName: getElementName, | ||
} | ||
}()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/** | ||
* GTM-Storage example of usage | ||
* | ||
* @link https://github.com/cichy380/GTM-Storage | ||
* @author Marcin Dobroszek | ||
* @license The MIT License (MIT) | ||
* | ||
* @todo Enhanced Ecommerce handling | ||
*/ | ||
;(function($, undefined) { | ||
'use strict'; | ||
|
||
/** | ||
* Read Storage data and send it to GTM | ||
*/ | ||
function sendGtmStorage() { | ||
var gtmData; | ||
|
||
if (typeof gtmStorage !== 'object') { | ||
$.error('Required object GTM Storage (gtmStorage) missing.'); | ||
} | ||
|
||
if (typeof dataLayer !== 'object') { | ||
$.error('Required object GTM (dataLayer) missing.'); | ||
} | ||
|
||
gtmData = gtmStorage.getItems(); | ||
gtmData.forEach(function (item, index) { | ||
var data2send = {}; | ||
|
||
if (item.sending === false) { // data item not sending yet | ||
data2send = $(item.element).data(gtmStorage.namespace); | ||
|
||
// check if required object and property exists | ||
if (typeof data2send === 'object' && typeof data2send.event !== 'undefined') { | ||
// sending data to GTM... | ||
dataLayer.push({ | ||
event: data2send.event, | ||
data: data2send.data || null, | ||
eventCallback: function () { | ||
// remove item data from storage after GTM callback | ||
gtmStorage.removeItem(item.id); | ||
}, | ||
}); | ||
|
||
// prevent double sending | ||
item.sending = true; | ||
gtmStorage.editItem(item, item.id); | ||
} | ||
else { | ||
// item data is invalid - remove it | ||
gtmStorage.removeItem(item.id); | ||
window.console && console.error('HTML tag ' + gtmStorage.getElementName($(item.element).get(0)) + | ||
' does not have required data-gtm attribute or this attribute has wrong data format.'); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
// checking new data in GTM-Storage every 1.5 sec. | ||
setInterval(sendGtmStorage, 1500); | ||
})(jQuery); |
Oops, something went wrong.