Multi-Event Single Page Application Integration

If you have a Single Page Application (SPA), then integrating the Detection Tag requires a few more steps to work seamlessly with your application's lifecycle. This version of SPA support does not rely on specifically naming an HTML form to integrate, and it is capable of collecting signal from all page interactions.

Detection Tag SDK

The Detection Tag exposes a single, global function that starts signal collection and returns an object, which can be used to retrieve signals.

Multiple Tags On A Page

It is possible to run multiple tags on a single page, as long as there is only one tag per frame. This will yield the best detection results for pages with input forms rendered within iframes of a web application.

Example: one tag running on window.top (to capture and report on regular page interactions), one tag running inside an iframe (for a login form), one tag running inside a different iframe (for a registration form). This yields 3 seperate tags running in tandem, yet they are all isolated within their own frame depth.

Interface

window.$$$.start

Call this function as soon as the <script> tag has finished loading. Keep the resulting signalManager in a global store in your application, and use it across application views.

Syntax

let signalManager = window.$$$.start(options);

Parameters

Note: Any fields provided here will override the same field provided via the Detection tag query parameters.

options - Key-value mapping of options

`ap` - (optional, string) App ID
`ck` - (optional, string) Cookie ID
`dv` - (optional, string) Device ID
`si` - (optional, string) Site ID
`ui` - (optional, string) User ID 
`c1 - c10` - (optional, string) custom fields
`r1 - r10` - (optional, string) reporting-only fields

For detailed information on the full set of Detection Tag fields, refer to the Detection Tag Field Index.

signalManager.report

signalManager.report compiles all available signals and returns a promise that resolves into the signals. It has one required argument and one optional argument.

options - Key-value mapping of options eventType - (required, string) Event type to be reported resourceId - (optional, string) Resource ID of object being reported (song, video, article, etc)

Syntax

/*
`report` returns a promise that resolves into a signals object:
    {
        "OZ_TC": "", 
        "OZ_DT": "", 
        "OZ_SG": "" 
    }
*/
signalManager.report(options).then(signals => {
    // Do something with the provide signals
});

signalManager.update

signalManager.update is a function for changing the context of the payload, it allows for changing business signals to be sent back with the next call to report(). The following arguments can be updated:

options - Key-value mapping of options ap - (optional, string) App ID ck - (optional, string) Cookie ID dv - (optional, string) Device ID si - (optional, string) Site ID ui - (optional, string) User ID c1 - c10 - (optional, string) custom fields r1 - r10 - (optional, string) reporting-only fields

Syntax

signalManager.update(options);

Examples

Vanilla JS

<html lang="en">
<head>
<!-- Add this stub to prevent breaking if the tag gets blocked -->
<script>window.$$$={start:function(){return{report:function(){return{then:function(fn){fn({})}}},stop:function(){},update:function(){}}}};</script>
<script src="https://s.example.com/static/5.0.0/pagespeed.js?dt=1234567890123456789012&psv=5.0.0&pd=acc&mo=2&spa=1&di=www.example.com&ui=04a1ad8a40e296df0d385e46c3796cd3&ck=ddeedb6f4adf80dd2d8b0eb81c69da13"></script>
<title>Login</title>
</head>
<body>
  <h1>Log In</h1>
  <h2 id="error" class="error" style="display: none"></h2>
  <form id="login" method="POST">
    <input id="username" type="text" name="username"/>
    <input id="password" type="password" name="password"/>
    <button type="submit">Log In</button>
  </form>
</body>
</html>
// Calling start begins signal collection and creates a signal manager.
const signalManager = window.$$$.start({si: 'login', c1: 'v1', r1: 'asdf'});

window.addEventListener('DOMContentLoaded', () => {
  const loginHandler = event => {
    // Prevent the form from submitting with a full page reload.
    event.preventDefault();

    const username = document.getElementById('username').value;
    const password = document.getElementById('password').value;
    const error = document.getElementById('error');

    // Do front-end validation before calling `signalManager.report`, and don't call it if
    // you're not going to submit the event.
    if (!username || !password) {
      error.innerHTML = 'Username and password are required.';
      error.style.display = 'block';
      return;
    }
    error.style.display = 'none';

    // Call report to get a promise that resolves into signals to pass along in your event submission.
    signalManager.report({eventType: '1'}).then(signals => {
      const xhr = new XMLHttpRequest();
      xhr.onload = () => {
        if (xhr.status === 200) {
          history.pushState({}, 'Dashboard', 'https://example.com/dashboard');
        } else if (xhr.status >= 400) {
          error.innerHTML = xhr.statusText;
          error.style.display = 'block';
        }
      };
      // Object.assign merges your data into the signals object to ensure everything propagates to your server.
      const json = Object.assign(signals, {username, password});
      xhr.open('POST', 'https://example.com/api/login');
      xhr.setRequestHeader('Content-Type', 'application/json');
      xhr.send(JSON.stringify(json));
    }).catch(error => {
      // Promise returned by `report` failed and propagated error here.
      error.innerHTML = error;
      error.style.display = 'block';
    });
  };

  // Hooking into the form `submit` event will catch cases where users hit the enter key in a text field.
  document.getElementById('login').addEventListener('submit', loginHandler, false);
});