Skip to content

Commit

Permalink
update for higher legibility
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksatr committed Jan 5, 2022
1 parent 1f8d213 commit 54186a5
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 39 deletions.
54 changes: 33 additions & 21 deletions modules/viewability.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const observers = {};

function isValid(vid, element, tracker, criteria) {
if (!element) {
logWarn(`${MODULE_NAME}: provide an html element to track`);
logWarn(`${MODULE_NAME}: no html element provided`);
return false;
}

Expand All @@ -37,13 +37,39 @@ function isValid(vid, element, tracker, criteria) {
}

if (!vid || observers[vid]) {
logWarn(`${MODULE_NAME}: provide an unregistered vid`, vid);
logWarn(`${MODULE_NAME}: must provide an unregistered vid`, vid);
return false;
}

return true;
}

function stopObserving(observer, vid, element) {
observer.unobserve(element);
observers[vid].done = true;
}

function fireViewabilityTracker(element, tracker) {
switch (tracker.method) {
case 'img':
triggerPixel(tracker.value, () => {
logInfo(`${MODULE_NAME}: viewability pixel fired`, tracker.value);
});
break;
case 'js':
insertHtmlIntoIframe(`<script src="${tracker.value}"></script>`);
break;
case 'callback':
tracker.value(element);
break;
}
}

function viewabilityCriteriaMet(observer, vid, element, tracker) {
stopObserving(observer, vid, element);
fireViewabilityTracker(element, tracker);
}

/**
* Start measuring viewability of an element
* @typedef {{ method: string='img','js','callback', value: string|function }} ViewabilityTracker { method: 'img', value: 'http://my.tracker/123' }
Expand All @@ -58,7 +84,7 @@ export function startMeasurement(vid, element, tracker, criteria) {
return;
}

let options = {
const options = {
root: null,
rootMargin: '0px',
threshold: criteria.inViewThreshold,
Expand All @@ -71,23 +97,7 @@ export function startMeasurement(vid, element, tracker, criteria) {

if (viewable) {
observers[vid].timeoutId = window.setTimeout(() => {
// stop observing
observer.unobserve(element);
observers[vid].done = true;

switch (tracker.method) {
case 'img':
triggerPixel(tracker.value, () => {
logInfo(`${MODULE_NAME}: viewability pixel fired`, tracker.value);
});
break;
case 'js':
insertHtmlIntoIframe(`<script src="${tracker.value}"></script>`);
break;
case 'callback':
tracker.value(element);
break;
}
viewabilityCriteriaMet(observer, vid, element, tracker);
}, criteria.timeInView);
} else if (observers[vid].timeoutId) {
window.clearTimeout(observers[vid].timeoutId);
Expand All @@ -103,6 +113,8 @@ export function startMeasurement(vid, element, tracker, criteria) {
};

observer.observe(element);

logInfo(`${MODULE_NAME}: startMeasurement called with:`, arguments);
}

/**
Expand All @@ -111,7 +123,7 @@ export function startMeasurement(vid, element, tracker, criteria) {
*/
export function stopMeasurement(vid) {
if (!vid || !observers[vid]) {
logWarn(`${MODULE_NAME}: provide a registered vid`, vid);
logWarn(`${MODULE_NAME}: must provide a registered vid`, vid);
return;
}

Expand Down
50 changes: 35 additions & 15 deletions modules/viewability.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,43 @@ Maintainer: atrajkovic@magnite.com
Module does not need any configuration, as long as you include it in your PBJS bundle.
Viewability module has only two functions `startMeasurement` and `stopMeasurement` which can be used to enable more complex viewability measurements. Since it allows tracking from within creative (possibly inside a safe frame) this module registers a message listener, for messages with a format that is described bellow.

`startMeasurement`
This function has 4 parameters when called directly with `pbjs.viewability.startMeasurement()`:
- vid: unique viewability identifier, used to reference particular tracker which can later be used to stop the measurement. It allows for multiple trackers, with different criteria to be registered for a given HTML element, independently. vid is not autogenerated by startMeasurement(), it needs to be provided by caller so that it doesn't have to be posted back to the source iframe (in case viewability is started from within the creative).
- element: reference to an HTML element that needs to be tracked.
- tracker: ViewabilityTracker is an object type with two properties, `method` ('img'|'js'|'callback') and `value` which can be an URL string for 'img' and 'js' trackers, or a function for 'callback' tracker. Example: `{ method: 'img', value: 'http://my.tracker/123' }`
- criteria: ViewabilityCriteria is an object type with two properties, `inViewThreshold` which is a number (0, 1.0] representing a percentage of viewable element we're aiming at, and `timeInView` which is a number of milliseconds that a given element needs to be in view continuously, above a threshold. Example: `{ inViewThreshold: 0.5, timeInView: 1000 }`
## `startMeasurement`

When a tracker needs to be started, without direct access to pbjs, postMessage mechanism can be used to invoke `startMeasurement`, with a following payload: `vid`, `tracker` and `criteria` as described above, but also with `message: 'Prebid Viewability'` and `action: 'startMeasurement'`. Optionally payload can provide `elementId`, if available at that time (for ad servers where name of the iframe is known, or adservers that render outside an iframe). If `elementId` is not provided, viewability module will try to find the iframe that corresponds to the message source.
| startMeasurement Arg Object | Scope | Type | Description | Example |
| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- |
| vid | Required | String | Unique viewability identifier, used to reference particular observer | `"ae0f9"` |
| element | Required | HTMLElement | Reference to an HTML element that needs to be tracked | `document.getElementById('test_div')` |
| tracker | Required | ViewabilityTracker | How viewaility event is communicated back to the parties of interest | `{ method: 'img', value: 'http://my.tracker/123' }` |
| criteria | Required | ViewabilityCriteria| Defines custom viewability criteria using the threshold and duration provided | `{ inViewThreshold: 0.5, timeInView: 1000 }` |

| ViewabilityTracker | Scope | Type | Description | Example |
| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- |
| method | Required | String | Type of method for Tracker | `'img' OR 'js' OR 'callback'` |
| value | Required | String | URL string for 'img' and 'js' Trackers, or a function for 'callback' Tracker | `'http://my.tracker/123'` |

`stopMeasurement`:
This function has only 1 parementer when called directly with `pbjs.viewability.stopMeasurement()`:
- vid: unique viewability identifier, referencing an already started viewability tracker.
| ViewabilityCriteria | Scope | Type | Description | Example |
| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- |
| inViewThreshold | Required | Number | Represents a percentage threshold for the Element to be registered as in view | `0.5` |
| timeInView | Required | Number | Number of milliseconds that a given element needs to be in view continuously, above the threshold | `1000` |

When a tracker needs to be stopped, without direct access to pbjs, postMessage mechanism can be used here as well, to invoke `stopMeasurement`, providing payload with `vid`, `message: 'Prebid Viewability'` and `action: 'stopMeasurement`.
## Please Note:
- `vid` allows for multiple trackers, with different criteria to be registered for a given HTML element, independently. It's not autogenerated by `startMeasurement()`, it needs to be provided by the caller so that it doesn't have to be posted back to the source iframe (in case viewability is started from within the creative).
- In case of 'callback' method, HTML element is being passed back to the callback function.
- When a tracker needs to be started, without direct access to pbjs, postMessage mechanism can be used to invoke `startMeasurement`, with a following payload: `vid`, `tracker` and `criteria` as described above, but also with `message: 'Prebid Viewability'` and `action: 'startMeasurement'`. Optionally payload can provide `elementId`, if available at that time (for ad servers where name of the iframe is known, or adservers that render outside an iframe). If `elementId` is not provided, viewability module will try to find the iframe that corresponds to the message source.

# Example of starting a viewability measurement, when you have direct access to pbjs

## `stopMeasurement`

| stopMeasurement Arg Object | Scope | Type | Description | Example |
| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- |
| vid | Required | String | Unique viewability identifier, referencing an already started viewability tracker. | `"ae0f9"` |

## Please Note:
- When a tracker needs to be stopped, without direct access to pbjs, postMessage mechanism can be used here as well. To invoke `stopMeasurement`, you provide the payload with `vid`, `message: 'Prebid Viewability'` and `action: 'stopMeasurement`. Check the example bellow.

# Examples

## Example of starting a viewability measurement, when you have direct access to pbjs
```
pbjs.viewability.startMeasurement(
'ae0f9',
Expand All @@ -37,7 +57,7 @@ pbjs.viewability.startMeasurement(
);
```

# Example of starting a viewability measurement from within a rendered creative
## Example of starting a viewability measurement from within a rendered creative
```
let viewabilityRecord = {
vid: 'ae0f9',
Expand All @@ -50,12 +70,12 @@ let viewabilityRecord = {
window.parent.postMessage(JSON.stringify(viewabilityRecord), '*');
```

# Example of stopping the viewability measurement, when you have direct access to pbjs
## Example of stopping the viewability measurement, when you have direct access to pbjs
```
pbjs.viewability.stopMeasurement('ae0f9');
```

# Example of stopping the viewability measurement from within a rendered creative
## Example of stopping the viewability measurement from within a rendered creative
```
let viewabilityRecord = {
vid: 'ae0f9',
Expand Down
6 changes: 3 additions & 3 deletions test/spec/modules/viewability_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ describe('viewability test', () => {

viewability.startMeasurement('4', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { inViewThreshold: 0.5, timeInView: 1000 });
expect(logWarnSpy.callCount).to.equal(1);
expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: provide an unregistered vid`, '4')).to.equal(true);
expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: must provide an unregistered vid`, '4')).to.equal(true);
});

it('should check for valid criteria', () => {
Expand All @@ -94,7 +94,7 @@ describe('viewability test', () => {
let logWarnSpy = sandbox.spy(utils, 'logWarn');
viewability.startMeasurement('7', undefined, { method: 'js', value: 'http://my.tracker/123.js' }, { timeInView: 1000 });
expect(logWarnSpy.callCount).to.equal(1);
expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: provide an html element to track`)).to.equal(true);
expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: no html element provided`)).to.equal(true);
});
});

Expand Down Expand Up @@ -162,7 +162,7 @@ describe('viewability test', () => {
let logWarnSpy = sandbox.spy(utils, 'logWarn');
viewability.stopMeasurement('100');
expect(logWarnSpy.callCount).to.equal(1);
expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: provide a registered vid`, '100')).to.equal(true);
expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: must provide a registered vid`, '100')).to.equal(true);
});
});

Expand Down

0 comments on commit 54186a5

Please sign in to comment.