If your Shopify store has a custom theme you may need to bypass our standard process where our app inserts a tracking code at the top of your main Shopify theme file.

Sometimes the tracking of clicks on product list elements can interfere with custom scripts on your store, in which case you may need an adapted script.

If this applies to you, here are the steps:

  1. Ask the Littledata support team to flag your site as a 'custom install'.
  2. Go ahead and install the Littledata app. This will audit your data, and listen out for orders in Shopify, but NOT make any changes to your store theme.
  3. Once installed, copy the script below into a snippet and insert in your main theme (to load on all pages), at the top of the <body>  element.
  4. Change the webPropertyId on line 3 to be the test web analytics property ID that Littledata sets up.
  5. When you go live through Littledata, and merge the tracking, you'll need to update the webPropertyId on line 3 to reflect the live tracking ID.
<script name="littledata-tracking-tag">
        var LittledataLayer = {
   webPropertyID: '',
            cartPollInterval: 2000, //milliseconds between checking for new addsToCart
};
(function(){
//LittledataLayer to provide product details for Fix Google Analytics app
//Version CUSTOM


LittledataLayer.ecommerce = {
'currencyCode' : '{{shop.currency}}',
'impressions' : []
};

{%if product%}
var product = {{ product | json }};
if (product && !product.error) {
LittledataLayer.ecommerce.detail = {products: []}
LittledataLayer.ecommerce.detail.products.push({
id: product.id,
name: product.title,
price: (product.price/100).toFixed(0),
brand: product.vendor,
category: product.type,
variants: product.variants.map(function(variant) { return { id: variant.id, sku : variant.sku } }),
quantity: 1
});
}
{% elsif search %}
var searchResults = {{search.results | json}} || {};
{% else %}
var collectionNames = [];
{% if collection.handle == "all" %}
var collections = {{collections | json}};
{% else %}
var collections = [{{collection | json}}];
{% endif %}

if (collections && !collections.error) {
collections.forEach(function(c){
if (c) collectionNames.push(c.handle);
});
}

var collectionProducts = {{ collection.products | json }};
if (collectionProducts && !collectionProducts.error) {
collectionNames.forEach(function(c){
var pos = 1;
collectionProducts.forEach(function(p){
LittledataLayer.ecommerce.impressions.push({
id: p.id,
name: p.title,
price: (p.price/100).toFixed(0),
brand: p.vendor,
category: p.type,
position : pos++,
variants : p.variants.map(function(variant) { return { id: variant.id , sku : variant.sku } }),
list : location.pathname,
handle : p.handle
})
})
});
}
{% endif%}

LittledataLayer.ecommerce.impressions = getUniqueProducts(LittledataLayer.ecommerce.impressions)

function getUniqueProducts(array){
  var u = {}, a = [];
  for(var i = 0, l = array.length; i < l; ++i){
 if(u.hasOwnProperty(array[i].id)) {
continue;
 }
 a.push(array[i]);
 u[array[i].id] = 1;
  }
  return a;
}

var loadScript = function(url, callback){
var script = document.createElement("script")
script.type = "text/javascript";

if (script.readyState){  //IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" ||
script.readyState == "complete"){
script.onreadystatechange = null;
callback();
}
};
} else {  //Others
script.onload = function(){
callback();
};
}

script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
var ldTracker = function(jQuery){
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

ga('create', LittledataLayer.webPropertyID, 'auto', {name : 'littledata', allowLinker : true});
ga('littledata.require', 'displayfeatures');
ga('littledata.require','ec');
ga('littledata.require','linker');
ga('littledata.set', 'anonymizeIp', true);
ga('linker:autoLink', ['shopify.com','rechargeapps.com'])

jQuery(document).ready(function(){
var referralExclusion = /(paypal|visa|MasterCard|clicksafe|arcot\.com|geschuetzteinkaufen|checkout\.shopify\.com|checkout\.rechargeapps\.com|portal\.afterpay\.com|payfort)/
if ( referralExclusion.test(document.referrer) ) ga('littledata.set','referrer',null)

if(LittledataLayer && LittledataLayer.ecommerce){
var locationArr = location.pathname.split('/');
//run list and product scripts everywhere
listViewScript();
productPageScript();

if(locationArr[1] == 'cart')
cartPageScript();
}

function removePii(string) {
var piiRegex = {
"email": /[\s&amp;\/,=]([a-zA-Z0-9_.+-]+(\@|%40)[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)($|[\s&amp;\/,])/,
"postcode": /[\s&amp;\/,=]([A-Z]{1,2}[0-9][0-9A-Z]?(\s|%20)[0-9][A-Z]{2})($|[\s&amp;\/,])/,
};

var dlRemoved = string;
for (key in piiRegex) {
dlRemoved = dlRemoved.replace(piiRegex[key], 'REMOVED');
}
return dlRemoved;
}

ga('littledata.set', 'title', removePii(document.title));
ga('littledata.set', 'location', removePii(document.location.href));
ga('littledata.send', 'pageview')
})

function listViewScript(){
if (!LittledataLayer.ecommerce.impressions || !LittledataLayer.ecommerce.impressions.length) return;
window.setTimeout(function(){
LittledataLayer.ecommerce.impressions.forEach(function(product,index){
if(index <= 30)
ga('littledata.ec:addImpression', product)
})
ga('littledata.send', 'event', 'Ecommerce', 'Impressions', location.pathname, 0,{
nonInteraction: true,
hitCallback: function() {
}
});
},500) // wait for pageview to fire first

//product list clicks
jQuery('a')
.filter(function(){ return this.href.indexOf('/products') !== -1}) //only add event to products
.click(function(ev){
ev.preventDefault();
var self = this;
var clickedProductDetails = LittledataLayer.ecommerce.impressions.filter(function(product){
var linkSplit = self.href.split('/products/')
var productLink = linkSplit && linkSplit[1]
return productLink == product.handle
})[0];
if (clickedProductDetails) {
//only wait 1 second before redirecting
self.timeout = window.setTimeout(function(){
document.location = self.href;
},1000)
if(typeof localStorage != 'undefined')
localStorage.list = location.pathname;
ga('littledata.ec:addProduct', clickedProductDetails);
ga('littledata.ec:setAction', 'click', {list: location.pathname});
// Send click with an event, then send user to product page.
ga('littledata.send', 'event', 'Ecommerce', 'Product list click', String(clickedProductDetails.id), clickedProductDetails.price , {
hitCallback: function() {
 window.clearTimeout(self.timeout)
 document.location = self.href;
}
});
}
else document.location = self.href;
})
}

function productPageScript(){
var product = LittledataLayer.ecommerce.detail
&& LittledataLayer.ecommerce.detail.products
&& LittledataLayer.ecommerce.detail.products[0]

if (product) {
ga('littledata.ec:addProduct', product)

if(typeof localStorage !== 'undefined')
ga('littledata.ec:setAction', 'detail', {list : localStorage && localStorage.list});
else
ga('littledata.ec:setAction', 'detail');

ga('littledata.send', 'event', 'Ecommerce', 'Detail View', String(product.id), product.price, {
nonInteraction: true
});
}

var tracker = new CartTracker();
tracker.onChange(function(changes) {
changes.added.forEach( function(addedProduct) {
if (product) {
product.variant = addedProduct.variant_id;
}

var productPrice = (addedProduct.price/100).toFixed(0);

ga('littledata.ec:addProduct', product || {
id: addedProduct.product_id,
name: addedProduct.product_title,
price: productPrice,
brand: addedProduct.vendor,
category: addedProduct.product_type,
position : 1,
list : location.pathname,
handle : addedProduct.handle,
variant: addedProduct.variant_id
});
ga('littledata.ec:setAction', 'add');
ga('littledata.send', 'event', 'Ecommerce', 'Add to cart', addedProduct.product_id, productPrice);

window.setTimeout(function(){
cartPageScript();
},500)//wait for real cartToken to generate
})

changes.removed.forEach( function (removedProduct) {
if (product) {
product.variant = removedProduct.variant_id;
}


var productPrice = (removedProduct.price/100).toFixed(0);
ga('littledata.ec:addProduct', product || {
id: removedProduct.product_id,
name: removedProduct.product_title,
price: productPrice,
brand: removedProduct.vendor,
category: removedProduct.product_type,
position : 1,
list : location.pathname,
handle : removedProduct.handle,
variant: removedProduct.variant_id
});
ga('littledata.ec:setAction', 'remove');
ga('littledata.send', 'event', 'Ecommerce', 'Removed from cart', removedProduct.product_id, productPrice);
})
})
}

function cartPageScript(){
ga(function() {
  var clientID = ga.getByName('littledata').get('clientId');
jQuery.getJSON('/cart.json',function(cart){
jQuery.post('https://transactions.littledata.io/clientID', {
clientID : clientID,
cartID : cart.token
})
})
});
}

function CartTracker() {
var _cart = typeof localStorage !== 'undefined' && localStorage.ldCart && JSON.parse(localStorage.ldCart)
var _interval;
var _onChangeCbs = [];
var self = this;

this.start = function() {
interval = setInterval(function() {
fetchCart(function(newCart) {
if (JSON.stringify(_cart.items) !== JSON.stringify(newCart.items)) {
console.log('Changes to cart detected');
_onChangeCbs.forEach(function(_onChangeCb) {
_onChangeCb(getChanges(_cart, newCart))
})
_cart = newCart;
localStorage.ldCart = JSON.stringify(newCart);
}

})
}, LittledataLayer.cartPollInterval)
}

function fetchCart(_cb) {
jQuery.getJSON('/cart.json', function(__cart) {
_cb(__cart);
})
}

function getChanges(pastCart, currentCart) {
var pastCartProds = pastCart.items.map(cloneObject)
var currentCardProds = currentCart.items.map(cloneObject)

var changes = pastCartProds.length ? getArrChanges(pastCartProds, currentCardProds) : getArrChanges(currentCardProds, pastCartProds);
function getArrChanges(cart1, cart2) {
var returnVal = cart1
.map(function(pastProd) {
var currentCartProd = cart2.filter(function(prod) {
if (prod.id === pastProd.id) {
prod.analysed = true;
return true
}

return false;
})[0]
var returnProd = cloneObject(pastProd);
if (!currentCartProd) {
returnProd.quantity = -returnProd.quantity;
return returnProd;
}

returnProd.quantity = currentCartProd.quantity - pastProd.quantity;

return returnProd;
})
.concat(cart2.filter(function(currentProd) { // in case cart2 has new prods that were not in cart1
return !currentProd.analysed
}))

return returnVal
}

function unwindProds(arr) {
var returnArr = [];

arr.forEach(function(prod) {
var i;
var clonedProduct;
var quantity = Math.abs(prod.quantity);
clonedProduct = cloneObject(prod);
clonedProduct.quantity = 1;

for (i = 0; i < quantity; i++) {
returnArr.push(clonedProduct);
}
})

return returnArr;
}

function cloneObject (item) {
return JSON.parse(JSON.stringify(item))
}

return {
added: unwindProds(changes
.filter(function (prod) {
return pastCartProds.length ? prod.quantity > 0 : prod.quantity < 0;
})),
removed: unwindProds(changes
.filter(function (prod) {
return pastCartProds.length ? prod.quantity < 0 : prod.quantity > 0;
})),
}
}

this.onChange = function (newCb) { _onChangeCbs.push(newCb) }
this.stop = function () { clearInterval(interval) }

if (!_cart) {
fetchCart(function(newCart) {
_cart = newCart;
localStorage.ldCart = JSON.stringify(newCart);
self.start();
})
} else {
self.start();
}
}
}

if(typeof jQuery === 'undefined'){
loadScript('//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js', function(){
jQuery191 = jQuery.noConflict(true);
ldTracker(jQuery191);
 });
} else {
 ldTracker(jQuery);
}
})()
</script>
Did this answer your question?