Web SPA Tag Implementation

What is Single Page Application (SPA)?

Single Page Application (SPA) is any web site which uses different techniques and frameworks to add/remove/update content dynamically without actual page reload.

Why is the SPA tag implementation different compared to regular sites?

Clickio ads codes incorporate many different features to make your monetization experience and revenues as high as possible. We use different techniques to optimize ads placement, performance, viewability and other parameters, while protecting the advertisers from ineligible traffic and keeping in mind the core web vitals and privacy compliance. 

Single Page Applications need to communicate with our tags, so that they have up-to-date information on page URL, content position and layout changes.

How to start

An example of a non-SPA tag looks like this:

<script async type='text/javascript' src='//s.clickiocdn.com/t/000000/360_light.js'></script>
<script class='__lxGc__' type='text/javascript'>
  ((__lxGc__=window.__lxGc__||{'s':{},'b':0})['s']['_000000']=__lxGc__['s']['_000000']||{'b':{}})['b']['_111111']={'i':__lxGc__.b++};
</script> 

This code triggers ad unit placement where it is installed. 000000 stands for the site id in Clickio and 111111 is the ad unit id.

To programmatically place ad units where desired, the following method should be used:

// init or read global queue variable
(window.__lxGc__=window.__lxGc__||{'s':{},'b':0}).cmd=window.__lxGc__.cmd||[];

// push command to the global queue
__lxGc__.cmd.push(function(){
	// render ad unit into the DOM element 
	__lxGc__.display('<%DOM_element_id%>', '_000000', '_111111');
});

This will render an ad unit into the DOM element specified by the passed id. The site id and the ad unit id should be copy-pasted from the original code, mind the prefixing underscore. There are two limitations:

    • the same DOM element can not be used to insert several ad units.
    • based on the previous, the DOM element id must be unique for each call, but the ad unit itself may be inserted as many times as needed

The global variable init/read line may be implemented only once in the head , but will not break anything if called for each ad unit.

This is enough to start monetizing the SPA with fixed-positioned ad units. Please read further to learn how to implement more complex ad unit types, such as in-article and sticky banners.

 
Integrating Clickio codes with the SPA

As already mentioned, to optimize the ads performance and prevent monetization errors it is highly recommended to implement the following solution when integrating Clickio tags into the workflow of the SPA.

First, the head codes must be placed on the page, along with fixed-positioned ad units. This will trigger the initial load of the codes and start the logic for the desired ad units.

Though the fixed ad units now can be directly inserted into the updated content with either of the codes, other complex ad units formats (smart, sticky, etc.) do not allow such kind of re-initialization - those require a special command. The following events should be used when SPA changes the content:

  var eventData = {
    'detail': {
      'spa': {
        'page': (new URL(window.location)).pathname,
        'node': document.querySelector('main.App'),
        'mode': 'changed'
      }
    }
  };

  // console.groupCollapsed('Clickio SPA: sending content changed event');
  // console.log(eventData);
  // console.groupEnd();

  document.body.dispatchEvent(new CustomEvent('clickio_content_changed', eventData));

The example code should be called when the SPA logic finishes content manipulation and all the changes (URL, title, etc.) are applied. It tells the Clickio code to re-initialize the ads placement process and render all the applicable ad units inside the specified node DOM element. The additional page can be used for  the identification of the event later, if the DOM element itself is not replaced, but populated with the changed content; it may be any unique identifier (URL in the example). The mode attribute is not yet used and may be omitted, but is provided to distinguish different sub-types of the events.

The custom event must be dispatched on the body DOM element, which is considered to be static (not replaced) during the process of the SPA workflow. Please contact our support team If it is incompatible with your SPA implementation.

 

Example of the universal solution template

The following code may be edited and used to trigger the content update event for testing purposes or if direct code integration is not possible:

(function () {
    // semaphore to protect against multiple executions
  	if (window.__clickio_events_handler_init__) {
        return false;
    }
    window.__clickio_events_handler_init__ = true;

    // method to find the base node of the app content
    var findContentNode = function () {
        return document.querySelector("main.MainApp.AppContent");
    };

  	// method to send the Clickio event
    var sendClickioChangedEvent = function () {
        // prepare the event object
      	var eventData = {
            'detail': {
                'spa': {
                    'page': (new URL(window.location)).pathname, // using page URL as the identifier
                    'node': findContentNode(),                   // find the content node
                    'mode': 'changed'                            // event sub-type
                }
            }
        };

      	// dump event data for debug purposes
        // console.groupCollapsed('Clickio SPA: sending content changed event');
        // console.log(eventData);
        // console.groupEnd();

      	// dispatch the prepared event
        document.body.dispatchEvent(new CustomEvent('clickio_content_changed', eventData));
    };

    var previousUrl = location.href;     // cache the current URL to compare against the renewed one
    var currentNode = findContentNode(); // cache the content node to compare against the renewed one

  	// create a MutationObserver to monitor page content changes
    var observer  = new MutationObserver(function (mutations) {
        // compare the current URL with the cached one on any DOM changes,
      	// to minimize the CPU load of the mechanism
      	if (location.href !== previousUrl) {
            // if the URL has changed, cache it and continue to next phase
          	previousUrl = location.href;

            // create a new temporary observer to trigger on content node replacement
            let nodeObserver = new MutationObserver(function (mutations_list) {
                let tmpContentNode;

                if (!document.body.contains(currentNode)    // if the previous cached content node already removed from the DOM
                    && (tmpContentNode = findContentNode()) // and a new one already available and found by the method
                ) {
                    // cache the new content node
                  	currentNode = tmpContentNode;
					// disconnect the temporary observer
                    nodeObserver.disconnect();
					// trigger the event on additional timeout,
                    // as we do not know the exact moment when the content is ready
                    setTimeout(function(){ sendClickioChangedEvent(); }, 250);
                }
            });

            // attach the temporary observer to the parent of the content node
            nodeObserver.observe(currentNode.parentNode, {subtree: false, childList: true});
        }
    });

  	// attach the observer to the document to be triggered on any DOM changes
    observer.observe(document, {subtree: true, childList: true});

  	// debug message for testing purposes
    // console.log('Clickio SPA: events handler installed');
})();

At least the CSS query selector (line 10) must be edited accordingly to match the desired SPA DOM structure and logic for this to work.

This solution tries to minimize the CPU load, but it’s still far less efficient than placing the event dispatch call in the code of the SPA exactly where it should logically be.

 

Conclusion

The provided information can be treated as the starting point of the integration process, as the SPA workflows and implementations can vary widely, so feel free to contact us for additional information and support. We are open for suggestions and ready to help you add Clickio solutions to your SPA.