Adding Keytiles tracking to a "one-pager" page

In this article we would like to give you some inspiration about how to add Keytiles tracking to "one-pager" pages - so visitor's scroll activity and the visited sections can appear in Keytiles nicely.

Content of this page

Prerequisites

Before you continue we assume you are already aware of the concept of Hit attributes and you took a quick look on Keytiles <meta> tags too.

Step 0 - our HTML code

Let's assume our HTML looks like this

<html>
	<head>
		<title>My one-pager page</title>
		...
		<!-- Keytiles meta tags are present - defining attribs of the one-pager page -->
		<meta name="kt:tileId" content="12345" />
		<meta name="kt:tileTitle" content="My one-pager page" />
		<meta name="kt:tileType" content="article" />
		<meta name="kt:tileGroupPath" content="/a/b" />
		<meta name="kt:tileUrl" content="https://mywebsite.com/a/b/my-one-pager-page" />
	</head>
	<body>
		<div id="page-content">
			
			<!-- first element of the one-pager -->
			<div class="sub-element" id="element1">
				<h1>My first element</h1>
				... your element content comes here ...
			</div>
			
			<!-- second element of the one-pager -->
			<div class="sub-element" id="element2">
				<h1>My second element</h1>
				... your element content comes here ...
			</div>

			... more elements are here
			
		</div>

		<script type="text/javascript">
				 
			/**
			 * Let's assume we have a mechanism in place already which is able
			 * to signal us if an element arrives to the screen or leaves the screen!
			 * 
			 * This is our callback function which is invoked by this
			 * @param {string} subElementId - which element?
			 * @param {boolean} isOnScreen - TRUE if element is got to the screen - FALSE if went out			 
			 */
			function onElementScreenStatusChanged(subElementId, isOnScreen) {
			}
		
		</script>

	</body>
</html>

Step 1 - making decisions

We need to stop for a second and decide: how would you like to see the visits on the TileView? More specifically: where should those sub-contents belong to in your content structure? Given the fact your "one-pager" page might also belong to somewhere in the structure himself... Right?

Let's continue with this more complicated case - let's respect the structural position of the "one-pager" page fully and let's organize all sub-elements under this!

What does it mean practically? Given the above HTML code and the Keytiles <meta> tags in the <head> section we see clearly: 
our  "one pager" page has the following hit attributes:

{
	"tileId": "12345",
	"tileType": "article",
	"tileGroupPath": "/a/b",
	"tileTitle": "My one-pager page",
	"tileUrl": "https://mywebsite.com/a/b/my-one-pager-page"
}

And what we want is that all sub-elements are under this page. The very important hit attribute at this point is: tileGroupPath.

So for every sub-elements we could have the following hit attributes:

{
	// we go with a "postfix" style tileId
	"tileId": "12345-<sub element id>",
	"tileType": "article",
	// they all will appear "under" the one-pager page - using its title derivative
	"tileGroupPath": "/a/b/<dash-cased title of the one-pager page>",
	"tileTitle": "<sub element title>",
	// in the url the right way is to use anchors
	"tileUrl": "https://mywebsite.com/a/b/my-one-pager-page#<sub element id>"
}

Step 2 - let's add scripts!

Let's add some JavaScript magic to the above HTML! Just go through and read the inline comments!

We are almost ready however we will have one more problem... Can you spot it? :-)

<html>
	<head>
		<title>My one-pager page</title>
		...
		<!-- Keytiles meta tags are present - defining attribs of the one-pager page -->
		<meta name="kt:tileId" content="12345" />
		<meta name="kt:tileTitle" content="My one-pager page" />
		<meta name="kt:tileType" content="article" />
		<meta name="kt:tileGroupPath" content="/a/b" />
		<meta name="kt:tileUrl" content="https://mywebsite.com/a/b/my-one-pager-page" />
	</head>
	<body>
		<script type="text/javascript">
		
			// let's create a global reference to our tracker object
			var keytilesTrackerApi = null;
			
			/**
			 * Our callback method
			 * @param {KeytilesTrackingApi} keytilesTrackerApiObject- the Keytiles API object instance
			 */
			function onKeytilesTrackerAvailable(keytilesTrackerApiObject) {
				// make tracker available globally
				keytilesTrackerApi = keytilesTrackerApiObject;
			}

			/**
			 * We need the help of the keytilesTrackingConfig object Keytiles recognizes
			 */
			var keytilesTrackingConfig = {
				// let's keep the auto-tracking mechanism - that
				// works well for the one-pager page itself
				autoTrackingEnabled: true,
				// and setup our callback function which will be called as soon as tracking script was initialized
				onTrackerAvailableCallbackFunc: onKeytilesTrackerAvailable
			};

			/**
			 * and now let's pull in the tracking script!
			 */
			(function () {
			  var kt = document.createElement('script');
			  kt.type = 'text/javascript';
			  kt.async = true;
			  kt.src = document.location.protocol+'//scripts.keytiles.com/tracking/<your-container-id>/stat.js';
			  var s = document.getElementsByTagName('script')[0];
			  s.parentNode.insertBefore(kt, s);
			})();
		</script>
	
		<div id="page-content">
			
			<!-- first element of the one-pager -->
			<div class="sub-element" id="element1">
				<h1>My first element</h1>
				... your element content comes here ...
			</div>
			
			<!-- second element of the one-pager -->
			<div class="sub-element" id="element2">
				<h1>My second element</h1>
				... your element content comes here ...
			</div>

            ... more elements are here
			
		</div>
		
		<script type="text/javascript">
			
			// we just want to send in hits once / sub-element so we will mark them
			var hitSentAlready = {};
			
			/**
			 * Let's assume we have a mechanism in place already which is able
			 * to signal us if an element arrives to the screen or leaves the screen!
			 * 
			 * This is our callback function which is invoked by this
			 * @param {string} subElementId - which element?
			 * @param {boolean} isOnScreen - TRUE if element is got to the screen - FALSE if went out			 
			 */
			function onElementScreenStatusChanged(subElementId, isOnScreen) {
				// if gets to the screen and we have not sent hit yet, do it!
				if(isOnScreen && !hitSentAlready[subElementId]) {
					// we dont want to do it again
					hitSentAlready[subElementId] = true;
					
					// this returns the hit attribs for the one-pager page itself
					var onepagerHitAttribs = KeytilesTrackingApi.buildHitOptions();
					// let's put together the attribs of the sub-element!
					var hitAttribs = {
						tileId: onepagerHitAttribs.tileId + '-' + subElementId,
						tileType: 'article',
						// lets append the one-pager dash-cased title
						tileGroupPath: onepagerHitAttribs.tileGroupPath + '/' + onepagerHitAttribs.tileTitle.toLowerCase().replace(/\s/g, "-"),
						// lets grab the title from h1 tag
						tileTitle: document.getElementById(subElementId).firstChild().innerHtml,
						tileUrl: onepagerHitAttribs.tileUrl + '#' + subElementId
					};
					// fire in the hit!
					keytilesTrackerApi.sendPageview(hitAttribs);
				}
			}
		
		</script>
	</body>
</html>

Step 3 - let's make it final!

Our problem is: async script loading...

The onElementScreenStatusChanged() method might be fired easily BEFORE the Keytiles tracking objects become available... If that happens then the KeytilesTrackingApi.buildHitOptions() call will fail because KeytilesTrackingApi is not available yet.

So we need to build a "hit queue" somehow which
a) collects hits until Keytiles tracking becomes available
b) fires the queued hits once this happens

So let's rework the code a bit! To clean up things and separate concerns we create the keytilesTracking helper object to deal with everything we need

The below code was not tested! Use as inspiration only! :-)

<html>
	<head>
		<title>My one-pager page</title>
		...
		<!-- Keytiles meta tags are present - defining attribs of the one-pager page -->
		<meta name="kt:tileId" content="12345" />
		<meta name="kt:tileTitle" content="My one-pager page" />
		<meta name="kt:tileType" content="article" />
		<meta name="kt:tileGroupPath" content="/a/b" />
		<meta name="kt:tileUrl" content="https://mywebsite.com/a/b/my-one-pager-page" />
	</head>
	<body>
		<script type="text/javascript">
		
			/**
			 * Let's encapsulate every problem into a helper object!
			 */
			var keytilesTracking = {
				_keytilesTrackerApi: null,
				_hitQueue: [],
				_hitSentAlready: {},
				
				/**
				 * Our callback method - moved here
				 * @param {KeytilesTrackingApi} keytilesTrackerApiObject- the Keytiles API object instance
				 */
				onKeytilesTrackerAvailable: function(keytilesTrackerApiObject) {
					keytilesTracking._keytilesTrackerApi = keytilesTrackerApiObject;
					// fire the queue!
					keytilesTracking._hitQueue.forEach(function(item){
						this._assembleAndSendHit(item);
					}, keytilesTracking);
					keytilesTracking._hitQueue = [];
				},
				
				/**
				 * Initiate a hit sending for a sub-element
				 */
				sendElementHit: function(subElementId) {
					if(keytilesTracking._hitSentAlready[subElementId]) {
						// ignore - we already sent this
						return;
					}
					// mark it so we do not send again
					keytilesTracking._hitSentAlready[subElementId] = true;
					
					if(keytilesTracking._keytilesTrackerApi) {
						// we can send immediately - API is available already
						keytilesTracking._assembleAndSendHit(subElementId);
					} else {
						// push to the queue - we send as soon as API becomes available
						keytilesTracking._hitQueue.push(subElementId);
					}
				},
				
				/**
				 * private method - creates and sends the hit of a sub-element
				 */
				_assembleAndSendHit: function(subElementId) {
					// this returns the hit attribs for the one-pager page itself
					var onepagerHitAttribs = KeytilesTrackingApi.buildHitOptions();
					// let's put together the attribs of the sub-element!
					var hitAttribs = {
						tileId: onepagerHitAttribs.tileId + '-' + subElementId,
						tileType: 'article',
						// lets append the one-pager dash-cased title
						tileGroupPath: onepagerHitAttribs.tileGroupPath + '/' + onepagerHitAttribs.tileTitle.toLowerCase().replace(/\s/g, "-"),
						// lets grab the title from h1 tag
						tileTitle: document.getElementById(subElementId).firstChild().innerHtml,
						// just add the anchor
						tileUrl: onepagerHitAttribs.tileUrl + '#' + subElementId
					};
					// fire in the hit!
					keytilesTracking._keytilesTrackerApi.sendPageview(hitAttribs);
				}
			};
		
			/**
			 * We need the help of the keytilesTrackingConfig object Keytiles recognizes
			 */
			var keytilesTrackingConfig = {
				// let's keep the auto-tracking mechanism - that
				// works well for the one-pager page itself
				autoTrackingEnabled: true,
				// and setup our callback function which will be called as soon as tracking script was initialized
				onTrackerAvailableCallbackFunc: keytilesTracking.onKeytilesTrackerAvailable
			};

			/**
			 * and now let's pull in the tracking script!
			 */
			(function () {
			  var kt = document.createElement('script');
			  kt.type = 'text/javascript';
			  kt.async = true;
			  kt.src = document.location.protocol+'//scripts.keytiles.com/tracking/<your-container-id>/stat.js';
			  var s = document.getElementsByTagName('script')[0];
			  s.parentNode.insertBefore(kt, s);
			})();
		</script>
	
		<div id="page-content">
			
			<!-- first element of the one-pager -->
			<div class="sub-element" id="element1">
				<h1>My first element</h1>
				... your element content comes here ...
			</div>
			
			<!-- second element of the one-pager -->
			<div class="sub-element" id="element2">
				<h1>My second element</h1>
				... your element content comes here ...
			</div>

            ... more elements are here
			
		</div>
		
		<script type="text/javascript">
			
			/**
			 * Let's assume we have a mechanism in place already which is able
			 * to signal us if an element arrives to the screen or leaves the screen!
			 * 
			 * This is our callback function which is invoked by this
			 * @param {string} subElementId - which element?
			 * @param {boolean} isOnScreen - TRUE if element is got to the screen - FALSE if went out			 
			 */
			function onElementScreenStatusChanged(subElementId, isOnScreen) {
				// if gets to the screen fire the hit
				// note: now the filtering is added to the keytilesTracking object!
				if(isOnScreen) {
					keytilesTracking.sendElementHit(subElementId);
				}
			}
		
		</script>
	</body>
</html>