Skip to content

Commit

Permalink
Upgrade mediaQueryTracker to 1.0 feature set
Browse files Browse the repository at this point in the history
  • Loading branch information
philipwalton committed Apr 26, 2016
1 parent 6b01487 commit 69f527b
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 53 deletions.
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,19 +160,21 @@ The following element would send an event hit to Google Analytics with the categ

The `mediaQueryTracker` plugin allows you to track what media query is active as well as how often the matching media query changes.

You can tell the `mediaQueryTracker` plugin what media query data to look for via the [`mediaQueryDefinitions`](#mediaquerydefinitions) configuration option.
You can tell the `mediaQueryTracker` plugin what media query data to look for via the [`definitions`](#definitions) configuration option.

**Important: unlike the other autotrack plugins, to use the `mediaQueryTracker` plugin you have to first make a few changes to your property settings in Google Analytics. Here's what needs to be done:**

1. Log in to Google Analytics, choose the [account and property](https://support.google.com/analytics/answer/1009618) you're sending data too, and [create a custom dimension](https://support.google.com/analytics/answer/2709829) for each set of media queries you want to track (e.g. Breakpoints, Resolution/DPI, Device Orientation)
2. Give each dimension a name (e.g. Breakpoints), select a scope of [hit](https://support.google.com/analytics/answer/2709828#example-hit), and make sure the "active" checkbox is checked.
3. In the [`mediaQueryDefinitions`](#mediaquerydefinitions) config object, set the `name` and `dimensionIndex` values to be the same as the name and index shown in Google Analytics.
3. In the [`definitions`](#definitions) config object, set the `name` and `dimensionIndex` values to be the same as the name and index shown in Google Analytics.

Refer to the [`mediaQueryDefinitions`](#mediaquerydefinitions) configuration option documentation for an example definition that will track breakpoint, device resolution, and device orientation data.
Refer to the [`definitions`](#definitions) configuration option documentation for an example definition that will track breakpoint, device resolution, and device orientation data.

#### Options

* [`mediaQueryDefinitions`](#mediaquerydefinitions)
* [`definitions`](#definitions)
* [`changeTemplate`](#changetemplate)
* [`changeTimeout`](#changetimeout)

### `outboundFormTracker`

Expand Down Expand Up @@ -252,7 +254,7 @@ The following options can be passed to the `autotrack` plugin or individual sub-

The attribute prefix for declarative event and social tracking. The value used after the prefix is a kebab-case version of the field name, for example: the field `eventCategory` with the prefix `'data-ga-'` would be `data-ga-event-category`.

### `mediaQueryDefinitions`
### `definitions`

**Type**: `Object|Array|null`

Expand All @@ -270,7 +272,7 @@ The following array is an example of three media query object definitions:

```js
ga('require', 'autotrack', {
mediaQueryDefinitions: [
definitions: [
{
name: 'Breakpoint',
dimensionIndex: 1,
Expand Down Expand Up @@ -303,7 +305,7 @@ ga('require', 'autotrack', {

If multiple `media` values match at the same time, the one specified later in the `items` array will take precedence. For example, in the "Breakpoint" example above, the item `sm` is set to `all`, so it will always match unless `md` or `lg` matches.

### `mediaQueryChangeTemplate`
### `changeTemplate`

**Type**: `Function`

Expand All @@ -317,7 +319,7 @@ function(newValue, oldValue) {

A function used to format the `eventLabel` of media query change events. For example, if the matched media changes from `lg` to `md`, by default the result will be `lg => md`.

### `mediaQueryChangeTimeout`
### `changeTimeout`

**Type**: `number`

Expand Down
3 changes: 2 additions & 1 deletion lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@


module.exports = {
DEV_ID: 'i5iSjo'
DEV_ID: 'i5iSjo',
NULL_DIMENSION: '(not set)'
};
43 changes: 22 additions & 21 deletions lib/plugins/media-query-tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,12 @@


var debounce = require('debounce');
var constants = require('../constants');
var provide = require('../provide');
var createFieldsObj = require('../utilities').createFieldsObj;
var defaults = require('../utilities').defaults;
var isObject = require('../utilities').isObject;
var toArray = require('../utilities').toArray;
var provide = require('../provide');


/**
* Sets the string to use when no custom dimension value is available.
*/
var NULL_DIMENSION = '(not set)';


/**
Expand All @@ -46,15 +42,17 @@ function MediaQueryTracker(tracker, opts) {
if (!window.matchMedia) return;

this.opts = defaults(opts, {
mediaQueryDefinitions: false,
mediaQueryChangeTemplate: this.changeTemplate,
mediaQueryChangeTimeout: 1000
definitions: false,
changeTemplate: this.changeTemplate,
changeTimeout: 1000,
fieldsObj: null,
hitFilter: null
});

// Exits early if media query data doesn't exist.
if (!isObject(this.opts.mediaQueryDefinitions)) return;
if (!isObject(this.opts.definitions)) return;

this.opts.mediaQueryDefinitions = toArray(this.opts.mediaQueryDefinitions);
this.opts.definitions = toArray(this.opts.definitions);
this.tracker = tracker;
this.changeListeners = [];

Expand All @@ -67,7 +65,7 @@ function MediaQueryTracker(tracker, opts) {
* and adds the change listeners.
*/
MediaQueryTracker.prototype.processMediaQueries = function() {
this.opts.mediaQueryDefinitions.forEach(function(definition) {
this.opts.definitions.forEach(function(definition) {
// Only processes definitions with a name and index.
if (definition.name && definition.dimensionIndex) {
var mediaName = this.getMatchName(definition);
Expand All @@ -94,7 +92,7 @@ MediaQueryTracker.prototype.getMatchName = function(definition) {
match = item;
}
});
return match ? match.name : NULL_DIMENSION;
return match ? match.name : constants.NULL_DIMENSION;
};


Expand All @@ -109,7 +107,7 @@ MediaQueryTracker.prototype.addChangeListeners = function(definition) {
var mql = getMediaListener(item.media);
var fn = debounce(function() {
this.handleChanges(definition);
}.bind(this), this.opts.mediaQueryChangeTimeout);
}.bind(this), this.opts.changeTimeout);

mql.addListener(fn);
this.changeListeners.push({mql: mql, fn: fn});
Expand All @@ -129,8 +127,14 @@ MediaQueryTracker.prototype.handleChanges = function(definition) {

if (newValue !== oldValue) {
this.tracker.set('dimension' + definition.dimensionIndex, newValue);
this.tracker.send('event', definition.name, 'change',
this.opts.mediaQueryChangeTemplate(oldValue, newValue));

var defaultFields = {
eventCategory: definition.name,
eventAction: 'change',
eventLabel: this.opts.changeTemplate(oldValue, newValue)
};
this.tracker.send('event', createFieldsObj(defaultFields,
this.opts.fieldsObj, this.tracker, this.opts.hitFilter));
}
};

Expand All @@ -142,15 +146,12 @@ MediaQueryTracker.prototype.remove = function() {
for (var i = 0, listener; listener = this.changeListeners[i]; i++) {
listener.mql.removeListener(listener.fn);
}
this.changeListeners = null;
this.tracker = null;
this.opts = null;
};


/**
* Sets the default formatting of the change event label.
* This can be overridden by setting the `mediaQueryChangeTemplate` option.
* This can be overridden by setting the `changeTemplate` option.
* @param {string} oldValue The value of the media query prior to the change.
* @param {string} newValue The value of the media query after the change.
* @return {string} The formatted event label.
Expand Down
128 changes: 105 additions & 23 deletions test/media-query-tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ var browserCaps;
var TIMEOUT = 1000;


var autotrackOpts = {
mediaQueryDefinitions: [
var opts = {
definitions: [
{
name: 'Width',
dimensionIndex: 1,
Expand Down Expand Up @@ -79,7 +79,7 @@ describe('mediaQueryTracker', function() {
if (notSupportedInBrowser()) return;

browser
.execute(ga.run, 'require', 'mediaQueryTracker', autotrackOpts)
.execute(ga.run, 'require', 'mediaQueryTracker', opts)
.waitUntil(ga.trackerDataMatches([
['dimension1', 'lg'],
['dimension2', 'md']
Expand All @@ -92,7 +92,7 @@ describe('mediaQueryTracker', function() {
if (notSupportedInBrowser()) return;

browser
.execute(ga.run, 'require', 'mediaQueryTracker', autotrackOpts)
.execute(ga.run, 'require', 'mediaQueryTracker', opts)
.setViewportSize({width: 400, height: 400}, false)
.waitUntil(ga.trackerDataMatches([
['dimension1', 'sm'],
Expand All @@ -116,7 +116,7 @@ describe('mediaQueryTracker', function() {
if (notSupportedInBrowser()) return;

browser
.execute(ga.run, 'require', 'mediaQueryTracker', autotrackOpts)
.execute(ga.run, 'require', 'mediaQueryTracker', opts)
.setViewportSize({width: 400, height: 400}, false);

var timeoutStart = Date.now();
Expand All @@ -139,7 +139,7 @@ describe('mediaQueryTracker', function() {

browser
.execute(ga.run, 'require', 'mediaQueryTracker',
Object.assign({}, autotrackOpts, {mediaQueryChangeTimeout: 0}))
Object.assign({}, opts, {changeTimeout: 0}))
.setViewportSize({width: 400, height: 400}, false);

var shortTimeoutStart = Date.now();
Expand All @@ -159,7 +159,7 @@ describe('mediaQueryTracker', function() {
.execute(ga.run, 'create', 'UA-XXXXX-Y', 'auto')
.execute(ga.trackHitData)
.setViewportSize({width: 800, height: 600}, false)
.execute(ga.run, 'require', 'mediaQueryTracker', autotrackOpts)
.execute(ga.run, 'require', 'mediaQueryTracker', opts)
.setViewportSize({width: 400, height: 400}, false);

var longTimeoutStart = Date.now();
Expand All @@ -183,14 +183,55 @@ describe('mediaQueryTracker', function() {
if (notSupportedInBrowser()) return;

browser
.execute(requireMediaQueryTrackerWithChangeTemplate)
.execute(requireMediaQueryTracker_changeTemplate)
.setViewportSize({width: 400, height: 400}, false)
.waitUntil(ga.hitDataMatches([
['[0].eventLabel', 'lg:sm'],
['[1].eventLabel', 'md:sm']
]));
});

it('should support customizing any field via the fieldsObj', function() {

if (notSupportedInBrowser()) return;

browser
.execute(ga.run, 'require', 'mediaQueryTracker',
Object.assign({}, opts, {
changeTimeout: 0,
fieldsObj: {
nonInteraction: true
}
}))
.setViewportSize({width: 400, height: 400}, false)
.waitUntil(ga.hitDataMatches([
['[0].eventCategory', 'Width'],
['[0].eventAction', 'change'],
['[0].eventLabel', 'lg => sm'],
['[0].nonInteraction', true],
['[1].eventCategory', 'Height'],
['[1].eventAction', 'change'],
['[1].eventLabel', 'md => sm'],
['[1].nonInteraction', true]
]));
});


it('should support specifying a hit filter', function() {

if (notSupportedInBrowser()) return;

browser
.execute(requireMediaQueryTracker_hitFilter)
.setViewportSize({width: 400, height: 400}, false)
.waitUntil(ga.hitDataMatches([
['[0].eventCategory', 'Height'],
['[0].eventAction', 'change'],
['[0].eventLabel', 'md => sm'],
['[0].nonInteraction', true]
]));
});


it('should include the &did param with all hits', function() {

Expand All @@ -203,14 +244,30 @@ describe('mediaQueryTracker', function() {
});


/**
* @return {boolean} True if the current browser doesn't support all features
* required for these tests.
*/
function notSupportedInBrowser() {
// TODO(philipwalton): Some capabilities aren't implemented, so we can't test
// against Edge right now. Wait for build 10532 to support setViewportSize
// https://dev.windows.com/en-us/microsoft-edge/platform/status/webdriver/details/

// IE9 doesn't support matchMedia, so it's not tested.
return browserCaps.browserName == 'MicrosoftEdge' ||
(browserCaps.browserName == 'internet explorer' &&
browserCaps.version == '9');
}


/**
* Since function objects can't be passed via parameters from server to
* client, this one-off function must be used to set the value for
* `mediaQueryChangeTemplate`.
* `changeTemplate`.
*/
function requireMediaQueryTrackerWithChangeTemplate() {
function requireMediaQueryTracker_changeTemplate() {
ga('require', 'mediaQueryTracker', {
mediaQueryDefinitions: [
definitions: [
{
name: 'Width',
dimensionIndex: 1,
Expand All @@ -230,24 +287,49 @@ function requireMediaQueryTrackerWithChangeTemplate() {
]
}
],
mediaQueryChangeTemplate: function(oldValue, newValue) {
changeTemplate: function(oldValue, newValue) {
return oldValue + ':' + newValue;
}
});
}


/**
* @return {boolean} True if the current browser doesn't support all features
* required for these tests.
* Since function objects can't be passed via parameters from server to
* client, this one-off function must be used to set the value for
* `hitFilter`.
*/
function notSupportedInBrowser() {
// TODO(philipwalton): Some capabilities aren't implemented, so we can't test
// against Edge right now. Wait for build 10532 to support setViewportSize
// https://dev.windows.com/en-us/microsoft-edge/platform/status/webdriver/details/

// IE9 doesn't support matchMedia, so it's not tested.
return browserCaps.browserName == 'MicrosoftEdge' ||
(browserCaps.browserName == 'internet explorer' &&
browserCaps.version == '9');
function requireMediaQueryTracker_hitFilter() {
ga('require', 'mediaQueryTracker', {
definitions: [
{
name: 'Width',
dimensionIndex: 1,
items: [
{name: 'sm', media: 'all'},
{name: 'md', media: '(min-width: 480px)'},
{name: 'lg', media: '(min-width: 640px)'}
]
},
{
name: 'Height',
dimensionIndex: 2,
items: [
{name: 'sm', media: 'all'},
{name: 'md', media: '(min-height: 480px)'},
{name: 'lg', media: '(min-height: 640px)'}
]
}
],
hitFilter: function(model) {
var category = model.get('eventCategory');
if (category == 'Width') {
throw 'Exclude width changes';
}
else {
model.set('nonInteraction', true);
}
}
});
}

0 comments on commit 69f527b

Please sign in to comment.