This repository has been archived by the owner on Jan 24, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
analytics.js
251 lines (217 loc) · 7.79 KB
/
analytics.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
(function(global, factory) {
if (typeof define === 'function' && define.amd) {
define(factory);
}
else if (typeof module === "object" && module && typeof module.exports === "object") {
module.exports = factory();
} else {
global.analytics = factory();
}
}(this, function() {
// Make sure _gaq is on the global so we don't die trying to access it
if(!this._gaq) {
this._gaq = [];
}
// Make sure optimizely is on the global so we don't die trying to access it
if(!this.optimizely) {
this.optimizely = [];
}
// Use hostname for GA Category
var _category = location.hostname,
_redacted = "REDACTED (Potential Email Address)";
/**
* To Title Case 2.1 - http://individed.com/code/to-title-case/
* Copyright 2008-2013 David Gouch. Licensed under the MIT License.
* https://github.com/gouch/to-title-case
*/
function toTitleCase(s){
var smallWords = /^(a|an|and|as|at|but|by|en|for|if|in|nor|of|on|or|per|the|to|vs?\.?|via)$/i;
s = trim(s);
return s.replace(/[A-Za-z0-9\u00C0-\u00FF]+[^\s-]*/g, function(match, index, title){
if (index > 0 &&
index + match.length !== title.length &&
match.search(smallWords) > -1 &&
title.charAt(index - 2) !== ":" &&
(title.charAt(index + match.length) !== '-' || title.charAt(index - 1) === '-') &&
title.charAt(index - 1).search(/[^\s-]/) < 0) {
return match.toLowerCase();
}
if (match.substr(1).search(/[A-Z]|\../) > -1) {
return match;
}
return match.charAt(0).toUpperCase() + match.substr(1);
});
}
// GA strings need to have leading/trailing whitespace trimmed, and not all
// browsers have String.prototoype.trim().
function trim(s) {
return s.replace(/^\s+|\s+$/g, '');
}
// See if s could be an email address. We don't want to send personal data like email.
function mightBeEmail(s) {
// There's no point trying to validate rfc822 fully, just look for ...@...
return (/[^@]+@[^@]+/).test(s);
}
function warn(msg) {
console.warn("[analytics] " + msg);
}
// Support both types of Google Analytics Event Tracking, see:
// ga.js - https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide
// analytics.js - https://developers.google.com/analytics/devguides/collection/analyticsjs/events
function _gaEvent(options) {
// If the new analytics.js API is present, fire this event using ga().
if(typeof ga === "function") {
// Transform the argument array to match the expected call signature for ga(), see:
// https://developers.google.com/analytics/devguides/collection/analyticsjs/events#overview
var fieldObject = {
'hitType': 'event',
'eventCategory': _category,
'eventAction': options.action
};
if(options.label) {
fieldObject['eventLabel'] = options.label;
}
if(options.value || options.value === 0) {
fieldObject['eventValue'] = options.value;
}
if(options.nonInteraction === true) {
fieldObject['nonInteraction'] = 1;
}
ga('send', fieldObject);
}
// Also support the old API. Google suggests firing data at both to be the right thing.
var eventArgs = ['_trackEvent', _category, options.action];
if(options.label) {
eventArgs[3] = options.label;
}
if(options.value || options.value === 0) {
eventArgs[4] = options.value;
}
if(options.nonInteraction === true) {
eventArgs[5] = true;
}
_gaq.push(eventArgs);
}
function event(action, options) {
options = options || {};
var eventOptions = {},
label = options.label,
value = options.value,
// Support both forms to deal with typos between the 2 APIs
nonInteraction = options.noninteraction || options.nonInteraction;
if(!action) {
warn("Expected `action` arg.");
return;
}
if(mightBeEmail(action)) {
warn("`action` arg looks like an email address, redacting.");
action = _redacted;
}
eventOptions.action = toTitleCase(action);
// label: An optional string to provide additional dimensions to the event data.
if(label) {
if(typeof label !== "string") {
warn("Expected `label` arg to be a String.");
} else {
if(mightBeEmail(label)) {
warn("`label` arg looks like an email address, redacting.");
label = _redacted;
}
eventOptions.label = trim(label);
}
}
// value: An optional integer that you can use to provide numerical data about
// the user event.
if(value || value === 0) {
if(typeof value !== "number") {
warn("Expected `value` arg to be a Number.");
} else {
// Force value to int
eventOptions.value = value|0;
}
}
// noninteraction: An optional boolean that when set to true, indicates that
// the event hit will not be used in bounce-rate calculation.
if(nonInteraction) {
if(typeof nonInteraction !== "boolean") {
warn("Expected `noninteraction` arg to be a Boolean.");
} else {
eventOptions.nonInteraction = nonInteraction === true;
}
}
_gaEvent(eventOptions);
}
// Use a consistent prefix and check if arg starts with a forward slash
function prefixVirtualPageview(s) {
// Bail if s is already prefixed.
if(/^\/virtual\//.test(s)) {
return s;
}
// Make sure s has a leading / then add prefix and return
s = s.replace(/^[/]?/, '/');
return '/virtual' + s;
}
// Support both types of Google Analytics Tracking, see:
// ga.js - https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_gat.GA_Tracker_._trackPageview
// analytics.js - https://developers.google.com/analytics/devguides/collection/analyticsjs/pages
function _gaVirtualPageView(options) {
// If the new analytics.js API is present, fire this event using ga().
if(typeof ga === "function") {
// Transform the argument array to match the expected call signature for ga():
// https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference
var fieldObject = {
'hitType': 'pageview',
'page': options.virtualPagePath
};
ga('send', fieldObject);
}
// Also support the old API. Google suggests firing data at both to be the right thing.
var eventArgs = ['_trackPageview', options.virtualPagePath];
_gaq.push(eventArgs);
}
function virtualPageview(virtualPagePath) {
if(!virtualPagePath) {
warn("Expected `virtualPagePath` arg.");
return;
}
virtualPagePath = trim(virtualPagePath);
var eventOptions = {};
eventOptions.virtualPagePath = prefixVirtualPageview(virtualPagePath);
_gaVirtualPageView(eventOptions);
}
function _optimizely(options) {
var eventArgs = ['trackEvent', options.action];
// check if we are giving this conversion financial value
if (options.revenue) {
var args = {
revenue: options.revenue
};
eventArgs[2] = args;
}
optimizely.push(eventArgs);
}
function conversionGoal(action, options) {
options = options || {};
var eventOptions = {},
valueInCents = options.valueInCents;
if(!action) {
warn("Expected `action` arg.");
return;
}
eventOptions.action = trim(action);
// valueInCents: An optional integer to track revenue - for example from fundraising appeal.
if(valueInCents) {
if((typeof valueInCents === 'number') && (valueInCents % 1 === 0)) {
eventOptions.revenue = valueInCents;
} else {
warn("Expected `valueInCents` arg to be an integer.");
}
}
_optimizely(eventOptions);
}
return {
event: event,
virtualPageview: virtualPageview,
conversionGoal: conversionGoal
};
}));