ahoy_matey 1.5.3 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +88 -0
  3. data/CONTRIBUTING.md +42 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +369 -380
  6. data/app/controllers/ahoy/base_controller.rb +15 -15
  7. data/app/controllers/ahoy/events_controller.rb +8 -2
  8. data/app/controllers/ahoy/visits_controller.rb +8 -1
  9. data/app/jobs/ahoy/geocode_job.rb +10 -0
  10. data/app/jobs/ahoy/geocode_v2_job.rb +28 -0
  11. data/config/routes.rb +1 -1
  12. data/lib/ahoy.rb +68 -86
  13. data/lib/ahoy/base_store.rb +97 -0
  14. data/lib/ahoy/controller.rb +26 -17
  15. data/lib/ahoy/database_store.rb +89 -0
  16. data/lib/ahoy/engine.rb +7 -7
  17. data/lib/ahoy/helper.rb +40 -0
  18. data/lib/ahoy/model.rb +5 -27
  19. data/lib/ahoy/{properties.rb → query_methods.rb} +25 -9
  20. data/lib/ahoy/tracker.rb +101 -42
  21. data/lib/ahoy/utils.rb +7 -0
  22. data/lib/ahoy/version.rb +1 -1
  23. data/lib/ahoy/visit_properties.rb +99 -37
  24. data/lib/generators/ahoy/activerecord_generator.rb +41 -0
  25. data/lib/generators/ahoy/base_generator.rb +13 -0
  26. data/lib/generators/ahoy/install_generator.rb +44 -0
  27. data/lib/generators/ahoy/mongoid_generator.rb +16 -0
  28. data/lib/generators/ahoy/templates/active_record_event_model.rb.tt +10 -0
  29. data/lib/generators/ahoy/templates/active_record_migration.rb.tt +62 -0
  30. data/lib/generators/ahoy/templates/active_record_visit_model.rb.tt +6 -0
  31. data/lib/generators/ahoy/templates/base_store_initializer.rb.tt +20 -0
  32. data/lib/generators/ahoy/templates/database_store_initializer.rb.tt +5 -0
  33. data/lib/generators/ahoy/{stores/templates/mongoid_event_model.rb → templates/mongoid_event_model.rb.tt} +4 -2
  34. data/lib/generators/ahoy/{stores/templates/mongoid_visit_model.rb → templates/mongoid_visit_model.rb.tt} +15 -9
  35. data/vendor/assets/javascripts/ahoy.js +336 -113
  36. metadata +54 -144
  37. data/.gitignore +0 -17
  38. data/Gemfile +0 -6
  39. data/Rakefile +0 -8
  40. data/ahoy_matey.gemspec +0 -38
  41. data/lib/ahoy/deckhands/location_deckhand.rb +0 -49
  42. data/lib/ahoy/deckhands/request_deckhand.rb +0 -52
  43. data/lib/ahoy/deckhands/technology_deckhand.rb +0 -47
  44. data/lib/ahoy/deckhands/traffic_source_deckhand.rb +0 -22
  45. data/lib/ahoy/deckhands/utm_parameter_deckhand.rb +0 -23
  46. data/lib/ahoy/geocode_job.rb +0 -13
  47. data/lib/ahoy/logger_silencer.rb +0 -75
  48. data/lib/ahoy/stores/active_record_store.rb +0 -60
  49. data/lib/ahoy/stores/active_record_token_store.rb +0 -113
  50. data/lib/ahoy/stores/base_store.rb +0 -88
  51. data/lib/ahoy/stores/bunny_store.rb +0 -33
  52. data/lib/ahoy/stores/fluentd_store.rb +0 -17
  53. data/lib/ahoy/stores/kafka_store.rb +0 -40
  54. data/lib/ahoy/stores/kinesis_firehose_store.rb +0 -42
  55. data/lib/ahoy/stores/log_store.rb +0 -53
  56. data/lib/ahoy/stores/mongoid_store.rb +0 -63
  57. data/lib/ahoy/subscribers/active_record.rb +0 -19
  58. data/lib/ahoy/throttle.rb +0 -17
  59. data/lib/generators/ahoy/stores/active_record_events_generator.rb +0 -53
  60. data/lib/generators/ahoy/stores/active_record_generator.rb +0 -16
  61. data/lib/generators/ahoy/stores/active_record_visits_generator.rb +0 -43
  62. data/lib/generators/ahoy/stores/bunny_generator.rb +0 -15
  63. data/lib/generators/ahoy/stores/custom_generator.rb +0 -15
  64. data/lib/generators/ahoy/stores/fluentd_generator.rb +0 -15
  65. data/lib/generators/ahoy/stores/kafka_generator.rb +0 -15
  66. data/lib/generators/ahoy/stores/kinesis_firehose_generator.rb +0 -15
  67. data/lib/generators/ahoy/stores/log_generator.rb +0 -15
  68. data/lib/generators/ahoy/stores/mongoid_events_generator.rb +0 -19
  69. data/lib/generators/ahoy/stores/mongoid_generator.rb +0 -14
  70. data/lib/generators/ahoy/stores/mongoid_visits_generator.rb +0 -27
  71. data/lib/generators/ahoy/stores/templates/active_record_event_model.rb +0 -12
  72. data/lib/generators/ahoy/stores/templates/active_record_events_migration.rb +0 -19
  73. data/lib/generators/ahoy/stores/templates/active_record_initializer.rb +0 -3
  74. data/lib/generators/ahoy/stores/templates/active_record_visit_model.rb +0 -4
  75. data/lib/generators/ahoy/stores/templates/active_record_visits_migration.rb +0 -57
  76. data/lib/generators/ahoy/stores/templates/bunny_initializer.rb +0 -9
  77. data/lib/generators/ahoy/stores/templates/custom_initializer.rb +0 -10
  78. data/lib/generators/ahoy/stores/templates/fluentd_initializer.rb +0 -3
  79. data/lib/generators/ahoy/stores/templates/kafka_initializer.rb +0 -9
  80. data/lib/generators/ahoy/stores/templates/kinesis_firehose_initializer.rb +0 -17
  81. data/lib/generators/ahoy/stores/templates/log_initializer.rb +0 -3
  82. data/lib/generators/ahoy/stores/templates/mongoid_initializer.rb +0 -3
  83. data/test/properties/mysql_json_test.rb +0 -18
  84. data/test/properties/mysql_text_test.rb +0 -19
  85. data/test/properties/postgresql_hstore_test.rb +0 -18
  86. data/test/properties/postgresql_json_test.rb +0 -18
  87. data/test/properties/postgresql_jsonb_test.rb +0 -18
  88. data/test/properties/postgresql_text_test.rb +0 -19
  89. data/test/test_helper.rb +0 -99
  90. data/test/visit_properties_test.rb +0 -44
@@ -2,24 +2,160 @@
2
2
  * Ahoy.js
3
3
  * Simple, powerful JavaScript analytics
4
4
  * https://github.com/ankane/ahoy.js
5
- * v0.1.0
5
+ * v0.3.4
6
6
  * MIT License
7
7
  */
8
8
 
9
- /*jslint browser: true, indent: 2, plusplus: true, vars: true */
9
+ (function (global, factory) {
10
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
11
+ typeof define === 'function' && define.amd ? define(factory) :
12
+ (global.ahoy = factory());
13
+ }(this, (function () { 'use strict';
10
14
 
11
- (function (window) {
12
- "use strict";
15
+ function isUndefined(value) {
16
+ return value === undefined;
17
+ }
18
+
19
+ function isNull(value) {
20
+ return value === null;
21
+ }
22
+
23
+ function isObject(value) {
24
+ return value === Object(value);
25
+ }
26
+
27
+ function isArray(value) {
28
+ return Array.isArray(value);
29
+ }
30
+
31
+ function isDate(value) {
32
+ return value instanceof Date;
33
+ }
34
+
35
+ function isBlob(value) {
36
+ return (
37
+ value &&
38
+ typeof value.size === 'number' &&
39
+ typeof value.type === 'string' &&
40
+ typeof value.slice === 'function'
41
+ );
42
+ }
43
+
44
+ function isFile(value) {
45
+ return (
46
+ isBlob(value) &&
47
+ (typeof value.lastModifiedDate === 'object' ||
48
+ typeof value.lastModified === 'number') &&
49
+ typeof value.name === 'string'
50
+ );
51
+ }
52
+
53
+ function isFormData(value) {
54
+ return value instanceof FormData;
55
+ }
56
+
57
+ function objectToFormData(obj, cfg, fd, pre) {
58
+ if (isFormData(cfg)) {
59
+ pre = fd;
60
+ fd = cfg;
61
+ cfg = null;
62
+ }
63
+
64
+ cfg = cfg || {};
65
+ cfg.indices = isUndefined(cfg.indices) ? false : cfg.indices;
66
+ cfg.nulls = isUndefined(cfg.nulls) ? true : cfg.nulls;
67
+ fd = fd || new FormData();
68
+
69
+ if (isUndefined(obj)) {
70
+ return fd;
71
+ } else if (isNull(obj)) {
72
+ if (cfg.nulls) {
73
+ fd.append(pre, '');
74
+ }
75
+ } else if (isArray(obj)) {
76
+ if (!obj.length) {
77
+ var key = pre + '[]';
78
+
79
+ fd.append(key, '');
80
+ } else {
81
+ obj.forEach(function(value, index) {
82
+ var key = pre + '[' + (cfg.indices ? index : '') + ']';
83
+
84
+ objectToFormData(value, cfg, fd, key);
85
+ });
86
+ }
87
+ } else if (isDate(obj)) {
88
+ fd.append(pre, obj.toISOString());
89
+ } else if (isObject(obj) && !isFile(obj) && !isBlob(obj)) {
90
+ Object.keys(obj).forEach(function(prop) {
91
+ var value = obj[prop];
92
+
93
+ if (isArray(value)) {
94
+ while (prop.length > 2 && prop.lastIndexOf('[]') === prop.length - 2) {
95
+ prop = prop.substring(0, prop.length - 2);
96
+ }
97
+ }
98
+
99
+ var key = pre ? pre + '[' + prop + ']' : prop;
100
+
101
+ objectToFormData(value, cfg, fd, key);
102
+ });
103
+ } else {
104
+ fd.append(pre, obj);
105
+ }
106
+
107
+ return fd;
108
+ }
109
+
110
+ var objectToFormdata = objectToFormData;
111
+
112
+ // https://www.quirksmode.org/js/cookies.html
113
+
114
+ var Cookies = {
115
+ set: function (name, value, ttl, domain) {
116
+ var expires = "";
117
+ var cookieDomain = "";
118
+ if (ttl) {
119
+ var date = new Date();
120
+ date.setTime(date.getTime() + (ttl * 60 * 1000));
121
+ expires = "; expires=" + date.toGMTString();
122
+ }
123
+ if (domain) {
124
+ cookieDomain = "; domain=" + domain;
125
+ }
126
+ document.cookie = name + "=" + escape(value) + expires + cookieDomain + "; path=/";
127
+ },
128
+ get: function (name) {
129
+ var i, c;
130
+ var nameEQ = name + "=";
131
+ var ca = document.cookie.split(';');
132
+ for (i = 0; i < ca.length; i++) {
133
+ c = ca[i];
134
+ while (c.charAt(0) === ' ') {
135
+ c = c.substring(1, c.length);
136
+ }
137
+ if (c.indexOf(nameEQ) === 0) {
138
+ return unescape(c.substring(nameEQ.length, c.length));
139
+ }
140
+ }
141
+ return null;
142
+ }
143
+ };
13
144
 
14
145
  var config = {
15
146
  urlPrefix: "",
16
147
  visitsUrl: "/ahoy/visits",
17
148
  eventsUrl: "/ahoy/events",
18
- cookieDomain: null,
19
149
  page: null,
20
150
  platform: "Web",
21
- useBeacon: false,
22
- startOnReady: true
151
+ useBeacon: true,
152
+ startOnReady: true,
153
+ trackVisits: true,
154
+ cookies: true,
155
+ cookieDomain: null,
156
+ headers: {},
157
+ visitParams: {},
158
+ withCredentials: false
23
159
  };
24
160
 
25
161
  var ahoy = window.ahoy || window.Ahoy || {};
@@ -52,46 +188,26 @@
52
188
  return config.urlPrefix + config.eventsUrl;
53
189
  }
54
190
 
191
+ function isEmpty(obj) {
192
+ return Object.keys(obj).length === 0;
193
+ }
194
+
55
195
  function canTrackNow() {
56
- return (config.useBeacon || config.trackNow) && canStringify && typeof(window.navigator.sendBeacon) !== "undefined";
196
+ return (config.useBeacon || config.trackNow) && isEmpty(config.headers) && canStringify && typeof(window.navigator.sendBeacon) !== "undefined" && !config.withCredentials;
57
197
  }
58
198
 
59
199
  // cookies
60
200
 
61
- // http://www.quirksmode.org/js/cookies.html
62
201
  function setCookie(name, value, ttl) {
63
- var expires = "";
64
- var cookieDomain = "";
65
- if (ttl) {
66
- var date = new Date();
67
- date.setTime(date.getTime() + (ttl * 60 * 1000));
68
- expires = "; expires=" + date.toGMTString();
69
- }
70
- var domain = config.cookieDomain || config.domain;
71
- if (domain) {
72
- cookieDomain = "; domain=" + domain;
73
- }
74
- document.cookie = name + "=" + escape(value) + expires + cookieDomain + "; path=/";
202
+ Cookies.set(name, value, ttl, config.cookieDomain || config.domain);
75
203
  }
76
204
 
77
205
  function getCookie(name) {
78
- var i, c;
79
- var nameEQ = name + "=";
80
- var ca = document.cookie.split(';');
81
- for (i = 0; i < ca.length; i++) {
82
- c = ca[i];
83
- while (c.charAt(0) === ' ') {
84
- c = c.substring(1, c.length);
85
- }
86
- if (c.indexOf(nameEQ) === 0) {
87
- return unescape(c.substring(nameEQ.length, c.length));
88
- }
89
- }
90
- return null;
206
+ return Cookies.get(name);
91
207
  }
92
208
 
93
209
  function destroyCookie(name) {
94
- setCookie(name, "", -1);
210
+ Cookies.set(name, "", -1);
95
211
  }
96
212
 
97
213
  function log(message) {
@@ -102,7 +218,7 @@
102
218
 
103
219
  function setReady() {
104
220
  var callback;
105
- while (callback = queue.shift()) {
221
+ while ((callback = queue.shift())) {
106
222
  callback();
107
223
  }
108
224
  isReady = true;
@@ -116,7 +232,36 @@
116
232
  }
117
233
  }
118
234
 
119
- // http://stackoverflow.com/a/2117523/1177228
235
+ function matchesSelector(element, selector) {
236
+ var matches = element.matches ||
237
+ element.matchesSelector ||
238
+ element.mozMatchesSelector ||
239
+ element.msMatchesSelector ||
240
+ element.oMatchesSelector ||
241
+ element.webkitMatchesSelector;
242
+
243
+ if (matches) {
244
+ return matches.apply(element, [selector]);
245
+ } else {
246
+ log("Unable to match");
247
+ return false;
248
+ }
249
+ }
250
+
251
+ function onEvent(eventName, selector, callback) {
252
+ document.addEventListener(eventName, function (e) {
253
+ if (matchesSelector(e.target, selector)) {
254
+ callback(e);
255
+ }
256
+ });
257
+ }
258
+
259
+ // http://beeker.io/jquery-document-ready-equivalent-vanilla-javascript
260
+ function documentReady(callback) {
261
+ document.readyState === "interactive" || document.readyState === "complete" ? callback() : document.addEventListener("DOMContentLoaded", callback);
262
+ }
263
+
264
+ // https://stackoverflow.com/a/2117523/1177228
120
265
  function generateId() {
121
266
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
122
267
  var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
@@ -125,47 +270,73 @@
125
270
  }
126
271
 
127
272
  function saveEventQueue() {
128
- // TODO add stringify method for IE 7 and under
129
- if (canStringify) {
273
+ if (config.cookies && canStringify) {
130
274
  setCookie("ahoy_events", JSON.stringify(eventQueue), 1);
131
275
  }
132
276
  }
133
277
 
134
- // from jquery-ujs
278
+ // from rails-ujs
135
279
 
136
280
  function csrfToken() {
137
- return $("meta[name=csrf-token]").attr("content");
281
+ var meta = document.querySelector("meta[name=csrf-token]");
282
+ return meta && meta.content;
138
283
  }
139
284
 
140
285
  function csrfParam() {
141
- return $("meta[name=csrf-param]").attr("content");
286
+ var meta = document.querySelector("meta[name=csrf-param]");
287
+ return meta && meta.content;
142
288
  }
143
289
 
144
290
  function CSRFProtection(xhr) {
145
291
  var token = csrfToken();
146
- if (token) xhr.setRequestHeader("X-CSRF-Token", token);
292
+ if (token) { xhr.setRequestHeader("X-CSRF-Token", token); }
147
293
  }
148
294
 
149
295
  function sendRequest(url, data, success) {
150
296
  if (canStringify) {
151
- $.ajax({
152
- type: "POST",
153
- url: url,
154
- data: JSON.stringify(data),
155
- contentType: "application/json; charset=utf-8",
156
- dataType: "json",
157
- beforeSend: CSRFProtection,
158
- success: success
159
- });
297
+ if ($) {
298
+ $.ajax({
299
+ type: "POST",
300
+ url: url,
301
+ data: JSON.stringify(data),
302
+ contentType: "application/json; charset=utf-8",
303
+ dataType: "json",
304
+ beforeSend: CSRFProtection,
305
+ success: success,
306
+ headers: config.headers,
307
+ xhrFields: {
308
+ withCredentials: config.withCredentials
309
+ }
310
+ });
311
+ } else {
312
+ var xhr = new XMLHttpRequest();
313
+ xhr.open("POST", url, true);
314
+ xhr.withCredentials = config.withCredentials;
315
+ xhr.setRequestHeader("Content-Type", "application/json");
316
+ for (var header in config.headers) {
317
+ if (config.headers.hasOwnProperty(header)) {
318
+ xhr.setRequestHeader(header, config.headers[header]);
319
+ }
320
+ }
321
+ xhr.onload = function() {
322
+ if (xhr.status === 200) {
323
+ success();
324
+ }
325
+ };
326
+ CSRFProtection(xhr);
327
+ xhr.send(JSON.stringify(data));
328
+ }
160
329
  }
161
330
  }
162
331
 
163
332
  function eventData(event) {
164
333
  var data = {
165
- events: [event],
166
- visit_token: event.visit_token,
167
- visitor_token: event.visitor_token
334
+ events: [event]
168
335
  };
336
+ if (config.cookies) {
337
+ data.visit_token = event.visit_token;
338
+ data.visitor_token = event.visitor_token;
339
+ }
169
340
  delete event.visit_token;
170
341
  delete event.visitor_token;
171
342
  return data;
@@ -191,9 +362,11 @@
191
362
  var data = eventData(event);
192
363
  var param = csrfParam();
193
364
  var token = csrfToken();
194
- if (param && token) data[param] = token;
195
- var payload = new Blob([JSON.stringify(data)], {type : "application/json; charset=utf-8"});
196
- navigator.sendBeacon(eventsUrl(), payload);
365
+ if (param && token) { data[param] = token; }
366
+ // stringify so we keep the type
367
+ data.events_json = JSON.stringify(data.events);
368
+ delete data.events;
369
+ window.navigator.sendBeacon(eventsUrl(), objectToFormdata(data));
197
370
  });
198
371
  }
199
372
 
@@ -201,15 +374,40 @@
201
374
  return config.page || window.location.pathname;
202
375
  }
203
376
 
377
+ function presence(str) {
378
+ return (str && str.length > 0) ? str : null;
379
+ }
380
+
381
+ function cleanObject(obj) {
382
+ for (var key in obj) {
383
+ if (obj.hasOwnProperty(key)) {
384
+ if (obj[key] === null) {
385
+ delete obj[key];
386
+ }
387
+ }
388
+ }
389
+ return obj;
390
+ }
391
+
204
392
  function eventProperties(e) {
205
- var $target = $(e.currentTarget);
206
- return {
207
- tag: $target.get(0).tagName.toLowerCase(),
208
- id: $target.attr("id"),
209
- "class": $target.attr("class"),
393
+ var target = e.target;
394
+ return cleanObject({
395
+ tag: target.tagName.toLowerCase(),
396
+ id: presence(target.id),
397
+ "class": presence(target.className),
210
398
  page: page(),
211
- section: $target.closest("*[data-section]").data("section")
212
- };
399
+ section: getClosestSection(target)
400
+ });
401
+ }
402
+
403
+ function getClosestSection(element) {
404
+ for ( ; element && element !== document; element = element.parentNode) {
405
+ if (element.hasAttribute('data-section')) {
406
+ return element.getAttribute('data-section');
407
+ }
408
+ }
409
+
410
+ return null;
213
411
  }
214
412
 
215
413
  function createVisit() {
@@ -219,15 +417,14 @@
219
417
  visitorId = ahoy.getVisitorId();
220
418
  track = getCookie("ahoy_track");
221
419
 
222
- if (visitId && visitorId && !track) {
420
+ if (config.cookies === false || config.trackVisits === false) {
421
+ log("Visit tracking disabled");
422
+ setReady();
423
+ } else if (visitId && visitorId && !track) {
223
424
  // TODO keep visit alive?
224
425
  log("Active visit");
225
426
  setReady();
226
427
  } else {
227
- if (track) {
228
- destroyCookie("ahoy_track");
229
- }
230
-
231
428
  if (!visitId) {
232
429
  visitId = generateId();
233
430
  setCookie("ahoy_visit", visitId, visitTtl);
@@ -248,7 +445,8 @@
248
445
  platform: config.platform,
249
446
  landing_page: window.location.href,
250
447
  screen_width: window.screen.width,
251
- screen_height: window.screen.height
448
+ screen_height: window.screen.height,
449
+ js: true
252
450
  };
253
451
 
254
452
  // referrer
@@ -256,9 +454,19 @@
256
454
  data.referrer = document.referrer;
257
455
  }
258
456
 
457
+ for (var key in config.visitParams) {
458
+ if (config.visitParams.hasOwnProperty(key)) {
459
+ data[key] = config.visitParams[key];
460
+ }
461
+ }
462
+
259
463
  log(data);
260
464
 
261
- sendRequest(visitsUrl(), data, setReady);
465
+ sendRequest(visitsUrl(), data, function () {
466
+ // wait until successful to destroy
467
+ destroyCookie("ahoy_track");
468
+ setReady();
469
+ });
262
470
  } else {
263
471
  log("Cookies disabled");
264
472
  setReady();
@@ -294,67 +502,77 @@
294
502
  ahoy.track = function (name, properties) {
295
503
  // generate unique id
296
504
  var event = {
297
- id: generateId(),
298
505
  name: name,
299
506
  properties: properties || {},
300
- time: (new Date()).getTime() / 1000.0
507
+ time: (new Date()).getTime() / 1000.0,
508
+ id: generateId(),
509
+ js: true
301
510
  };
302
511
 
303
- // wait for createVisit to log
304
- $( function () {
305
- log(event);
306
- });
307
-
308
512
  ready( function () {
309
- if (!ahoy.getVisitId()) {
513
+ if (config.cookies && !ahoy.getVisitId()) {
310
514
  createVisit();
311
515
  }
312
516
 
313
- event.visit_token = ahoy.getVisitId();
314
- event.visitor_token = ahoy.getVisitorId();
517
+ ready( function () {
518
+ log(event);
315
519
 
316
- if (canTrackNow()) {
317
- trackEventNow(event);
318
- } else {
319
- eventQueue.push(event);
320
- saveEventQueue();
520
+ event.visit_token = ahoy.getVisitId();
521
+ event.visitor_token = ahoy.getVisitorId();
321
522
 
322
- // wait in case navigating to reduce duplicate events
323
- setTimeout( function () {
324
- trackEvent(event);
325
- }, 1000);
326
- }
523
+ if (canTrackNow()) {
524
+ trackEventNow(event);
525
+ } else {
526
+ eventQueue.push(event);
527
+ saveEventQueue();
528
+
529
+ // wait in case navigating to reduce duplicate events
530
+ setTimeout( function () {
531
+ trackEvent(event);
532
+ }, 1000);
533
+ }
534
+ });
327
535
  });
536
+
537
+ return true;
328
538
  };
329
539
 
330
- ahoy.trackView = function () {
540
+ ahoy.trackView = function (additionalProperties) {
331
541
  var properties = {
332
542
  url: window.location.href,
333
543
  title: document.title,
334
544
  page: page()
335
545
  };
546
+
547
+ if (additionalProperties) {
548
+ for(var propName in additionalProperties) {
549
+ if (additionalProperties.hasOwnProperty(propName)) {
550
+ properties[propName] = additionalProperties[propName];
551
+ }
552
+ }
553
+ }
336
554
  ahoy.track("$view", properties);
337
555
  };
338
556
 
339
557
  ahoy.trackClicks = function () {
340
- $(document).on("click", "a, button, input[type=submit]", function (e) {
341
- var $target = $(e.currentTarget);
558
+ onEvent("click", "a, button, input[type=submit]", function (e) {
559
+ var target = e.target;
342
560
  var properties = eventProperties(e);
343
- properties.text = properties.tag == "input" ? $target.val() : $.trim($target.text().replace(/[\s\r\n]+/g, " "));
344
- properties.href = $target.attr("href");
561
+ properties.text = properties.tag == "input" ? target.value : (target.textContent || target.innerText || target.innerHTML).replace(/[\s\r\n]+/g, " ").trim();
562
+ properties.href = target.href;
345
563
  ahoy.track("$click", properties);
346
564
  });
347
565
  };
348
566
 
349
567
  ahoy.trackSubmits = function () {
350
- $(document).on("submit", "form", function (e) {
568
+ onEvent("submit", "form", function (e) {
351
569
  var properties = eventProperties(e);
352
570
  ahoy.track("$submit", properties);
353
571
  });
354
572
  };
355
573
 
356
574
  ahoy.trackChanges = function () {
357
- $(document).on("change", "input, textarea, select", function (e) {
575
+ onEvent("change", "input, textarea, select", function (e) {
358
576
  var properties = eventProperties(e);
359
577
  ahoy.track("$change", properties);
360
578
  });
@@ -367,24 +585,29 @@
367
585
  ahoy.trackChanges();
368
586
  };
369
587
 
370
- ahoy.start = function () {
371
- createVisit();
588
+ // push events from queue
589
+ try {
590
+ eventQueue = JSON.parse(getCookie("ahoy_events") || "[]");
591
+ } catch (e) {
592
+ // do nothing
593
+ }
372
594
 
373
- // push events from queue
374
- try {
375
- eventQueue = JSON.parse(getCookie("ahoy_events") || "[]");
376
- } catch (e) {
377
- // do nothing
378
- }
595
+ for (var i = 0; i < eventQueue.length; i++) {
596
+ trackEvent(eventQueue[i]);
597
+ }
379
598
 
380
- for (var i = 0; i < eventQueue.length; i++) {
381
- trackEvent(eventQueue[i]);
382
- }
599
+ ahoy.start = function () {
600
+ createVisit();
383
601
 
384
602
  ahoy.start = function () {};
385
603
  };
386
604
 
387
- if (config.startOnReady) { $(ahoy.start); }
605
+ documentReady(function() {
606
+ if (config.startOnReady) {
607
+ ahoy.start();
608
+ }
609
+ });
610
+
611
+ return ahoy;
388
612
 
389
- window.ahoy = ahoy;
390
- }(window));
613
+ })));