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 applications lifecycle.

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 and stop signal collection. It's important to call each of these functions at the appropriate time in your SPA's event lifecycle.

Interface

window.$$$.start

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

`et` - (required, string) Event type of form
`fi` - (required, string, may be empty) ID attribute of HTML form
`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.

Syntax

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

signalManager.stop

signalManager.stop stops all signal collection and renders the signalManager unusable. window.$$$.start must be called again to retrieve a new signalManager to restart collection.

Syntax

signalManager.stop();

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(){}}}};</script>
<script src="https://s.update.example.com/ag/123456/clear.js?dt=1234567890123456789012&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>
// Ensure that the form exists in the page before calling `window.$$$.start` or it will error.
window.addEventListener('DOMContentLoaded', () => {
  // Calling start begins signal collection and creates a signal manager.
  const signalManager = window.$$$.start({et: 1, fi: 'login', si: 'login', c1: 'v1', r1: 'asdf'});

  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().then(signals => {
      const xhr = new XMLHttpRequest();
      xhr.onload = () => {
        if (xhr.status === 200) {
          // Call stop to tear down the signal manager before loading another view.
          signalManager.stop();
          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);
});

React

import React, { Component } from 'react';
import axios from 'axios';
import { useHistory } from 'react-router-dom';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.history = useHistory();
    this.formId = 'login';
    this.signalManager = null;
    this.state = {
      error: ''
    };
  }

  componentDidMount() {
    // Calling start begins signal collection and creates a signal manager.
    this.signalManager = window.$$$.start({et: 1, fi: this.formId, si: 'login', c1: 'v1', r1: 'asdf'});
  }

  handleLogin(event) {
    // Prevent the form from submitting with a full page reload.
    event.preventDefault();

    const username = this.refs.username.value;
    const password = this.refs.password.value;

    // Do front-end validation before calling `signalManager.report`, and don't call it if
    // you're not going to submit the event.
    if (!username) {
      return this.setState({ error: 'Username is required' });
    } else if (!password) {
      return this.setState({ error: 'Password is required' });
    }
    this.setState({ error: '' });

    // Call report to get a promise that resolves into signals to pass along in your event submission.
    this.signalManager.report().then(signals => {
      const data = Object.assign(signals, {username, password});
      return axios.post('/login', data);
    }).then((response) => {
      // Call stop to tear down the signal manager before loading another view.
      this.signalManager.stop();
      this.history.push('/home');
    })
    .catch(function (error) {
      // An error occurred in either the promise returned by `report`
      // or the request to the /login endpoint, and the error propagated here.
      this.setState({ error });
    });
  }

  render() {
    return (
      <div>
        <h1>Log in</h1>
        {
          this.state.error &&
          <h2 class="error">{this.state.error}</h2>
        }
        <form id="login" onSubmit={this.handleLogin.bind(this)}>
          <input type="text" ref="username" />
          <input type="password" ref="password" />
          <button type="submit">Log In</button>
        </form>
      </div>
    )
  }
}

ReactDOM.render(<App/>, document.getElementById('app'));