ahoy_matey 1.5.5 → 4.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +184 -34
  3. data/CONTRIBUTING.md +42 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +464 -407
  6. data/app/controllers/ahoy/base_controller.rb +23 -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 +11 -0
  10. data/app/jobs/ahoy/geocode_v2_job.rb +31 -0
  11. data/config/routes.rb +1 -1
  12. data/lib/ahoy/base_store.rb +101 -0
  13. data/lib/ahoy/controller.rb +23 -16
  14. data/lib/ahoy/database_store.rb +94 -0
  15. data/lib/ahoy/engine.rb +14 -7
  16. data/lib/ahoy/helper.rb +40 -0
  17. data/lib/ahoy/model.rb +5 -27
  18. data/lib/ahoy/query_methods.rb +88 -0
  19. data/lib/ahoy/tracker.rb +105 -51
  20. data/lib/ahoy/utils.rb +7 -0
  21. data/lib/ahoy/version.rb +1 -1
  22. data/lib/ahoy/visit_properties.rb +99 -37
  23. data/lib/ahoy.rb +83 -93
  24. data/lib/ahoy_matey.rb +1 -1
  25. data/lib/generators/ahoy/activerecord_generator.rb +67 -0
  26. data/lib/generators/ahoy/base_generator.rb +13 -0
  27. data/lib/generators/ahoy/install_generator.rb +44 -0
  28. data/lib/generators/ahoy/mongoid_generator.rb +16 -0
  29. data/lib/generators/ahoy/templates/active_record_event_model.rb.tt +10 -0
  30. data/lib/generators/ahoy/templates/active_record_migration.rb.tt +62 -0
  31. data/lib/generators/ahoy/templates/active_record_visit_model.rb.tt +6 -0
  32. data/lib/generators/ahoy/templates/base_store_initializer.rb.tt +25 -0
  33. data/lib/generators/ahoy/templates/database_store_initializer.rb.tt +10 -0
  34. data/lib/generators/ahoy/{stores/templates/mongoid_event_model.rb → templates/mongoid_event_model.rb.tt} +4 -2
  35. data/lib/generators/ahoy/{stores/templates/mongoid_visit_model.rb → templates/mongoid_visit_model.rb.tt} +15 -9
  36. data/vendor/assets/javascripts/ahoy.js +271 -133
  37. metadata +37 -273
  38. data/.gitignore +0 -17
  39. data/Gemfile +0 -6
  40. data/Rakefile +0 -8
  41. data/ahoy_matey.gemspec +0 -38
  42. data/lib/ahoy/deckhands/location_deckhand.rb +0 -49
  43. data/lib/ahoy/deckhands/request_deckhand.rb +0 -52
  44. data/lib/ahoy/deckhands/technology_deckhand.rb +0 -47
  45. data/lib/ahoy/deckhands/traffic_source_deckhand.rb +0 -22
  46. data/lib/ahoy/deckhands/utm_parameter_deckhand.rb +0 -23
  47. data/lib/ahoy/geocode_job.rb +0 -13
  48. data/lib/ahoy/logger_silencer.rb +0 -75
  49. data/lib/ahoy/properties.rb +0 -58
  50. data/lib/ahoy/stores/active_record_store.rb +0 -61
  51. data/lib/ahoy/stores/active_record_token_store.rb +0 -114
  52. data/lib/ahoy/stores/base_store.rb +0 -88
  53. data/lib/ahoy/stores/bunny_store.rb +0 -33
  54. data/lib/ahoy/stores/fluentd_store.rb +0 -17
  55. data/lib/ahoy/stores/kafka_store.rb +0 -40
  56. data/lib/ahoy/stores/kinesis_firehose_store.rb +0 -42
  57. data/lib/ahoy/stores/log_store.rb +0 -53
  58. data/lib/ahoy/stores/mongoid_store.rb +0 -63
  59. data/lib/ahoy/stores/nats_store.rb +0 -34
  60. data/lib/ahoy/stores/nsq_store.rb +0 -36
  61. data/lib/ahoy/subscribers/active_record.rb +0 -19
  62. data/lib/ahoy/throttle.rb +0 -17
  63. data/lib/generators/ahoy/stores/active_record_events_generator.rb +0 -53
  64. data/lib/generators/ahoy/stores/active_record_generator.rb +0 -16
  65. data/lib/generators/ahoy/stores/active_record_visits_generator.rb +0 -43
  66. data/lib/generators/ahoy/stores/bunny_generator.rb +0 -15
  67. data/lib/generators/ahoy/stores/custom_generator.rb +0 -15
  68. data/lib/generators/ahoy/stores/fluentd_generator.rb +0 -15
  69. data/lib/generators/ahoy/stores/kafka_generator.rb +0 -15
  70. data/lib/generators/ahoy/stores/kinesis_firehose_generator.rb +0 -15
  71. data/lib/generators/ahoy/stores/log_generator.rb +0 -15
  72. data/lib/generators/ahoy/stores/mongoid_events_generator.rb +0 -19
  73. data/lib/generators/ahoy/stores/mongoid_generator.rb +0 -14
  74. data/lib/generators/ahoy/stores/mongoid_visits_generator.rb +0 -27
  75. data/lib/generators/ahoy/stores/nats_generator.rb +0 -15
  76. data/lib/generators/ahoy/stores/nsq_generator.rb +0 -15
  77. data/lib/generators/ahoy/stores/templates/active_record_event_model.rb +0 -12
  78. data/lib/generators/ahoy/stores/templates/active_record_events_migration.rb +0 -19
  79. data/lib/generators/ahoy/stores/templates/active_record_initializer.rb +0 -3
  80. data/lib/generators/ahoy/stores/templates/active_record_visit_model.rb +0 -4
  81. data/lib/generators/ahoy/stores/templates/active_record_visits_migration.rb +0 -57
  82. data/lib/generators/ahoy/stores/templates/bunny_initializer.rb +0 -9
  83. data/lib/generators/ahoy/stores/templates/custom_initializer.rb +0 -10
  84. data/lib/generators/ahoy/stores/templates/fluentd_initializer.rb +0 -3
  85. data/lib/generators/ahoy/stores/templates/kafka_initializer.rb +0 -9
  86. data/lib/generators/ahoy/stores/templates/kinesis_firehose_initializer.rb +0 -17
  87. data/lib/generators/ahoy/stores/templates/log_initializer.rb +0 -3
  88. data/lib/generators/ahoy/stores/templates/mongoid_initializer.rb +0 -3
  89. data/lib/generators/ahoy/stores/templates/nats_initializer.rb +0 -9
  90. data/lib/generators/ahoy/stores/templates/nsq_initializer.rb +0 -9
  91. data/test/properties/mysql_json_test.rb +0 -18
  92. data/test/properties/mysql_text_test.rb +0 -19
  93. data/test/properties/postgresql_hstore_test.rb +0 -18
  94. data/test/properties/postgresql_json_test.rb +0 -18
  95. data/test/properties/postgresql_jsonb_test.rb +0 -18
  96. data/test/properties/postgresql_text_test.rb +0 -19
  97. data/test/test_helper.rb +0 -99
  98. data/test/visit_properties_test.rb +0 -44
@@ -1,32 +1,72 @@
1
- /*
2
- * Ahoy.js
1
+ /*!
2
+ * Ahoy.js v0.4.2
3
3
  * Simple, powerful JavaScript analytics
4
4
  * https://github.com/ankane/ahoy.js
5
- * v0.2.1
6
5
  * MIT License
7
6
  */
8
7
 
9
- /*jslint browser: true, indent: 2, plusplus: true, vars: true */
10
-
11
- (function (window) {
12
- "use strict";
8
+ (function (global, factory) {
9
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
10
+ typeof define === 'function' && define.amd ? define(factory) :
11
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ahoy = factory());
12
+ })(this, (function () { 'use strict';
13
+
14
+ // https://www.quirksmode.org/js/cookies.html
15
+
16
+ var Cookies = {
17
+ set: function (name, value, ttl, domain) {
18
+ var expires = "";
19
+ var cookieDomain = "";
20
+ if (ttl) {
21
+ var date = new Date();
22
+ date.setTime(date.getTime() + (ttl * 60 * 1000));
23
+ expires = "; expires=" + date.toGMTString();
24
+ }
25
+ if (domain) {
26
+ cookieDomain = "; domain=" + domain;
27
+ }
28
+ document.cookie = name + "=" + escape(value) + expires + cookieDomain + "; path=/; samesite=lax";
29
+ },
30
+ get: function (name) {
31
+ var i, c;
32
+ var nameEQ = name + "=";
33
+ var ca = document.cookie.split(';');
34
+ for (i = 0; i < ca.length; i++) {
35
+ c = ca[i];
36
+ while (c.charAt(0) === ' ') {
37
+ c = c.substring(1, c.length);
38
+ }
39
+ if (c.indexOf(nameEQ) === 0) {
40
+ return unescape(c.substring(nameEQ.length, c.length));
41
+ }
42
+ }
43
+ return null;
44
+ }
45
+ };
13
46
 
14
47
  var config = {
15
48
  urlPrefix: "",
16
49
  visitsUrl: "/ahoy/visits",
17
50
  eventsUrl: "/ahoy/events",
18
- cookieDomain: null,
19
51
  page: null,
20
52
  platform: "Web",
21
- useBeacon: false,
22
- startOnReady: true
53
+ useBeacon: true,
54
+ startOnReady: true,
55
+ trackVisits: true,
56
+ cookies: true,
57
+ cookieDomain: null,
58
+ headers: {},
59
+ visitParams: {},
60
+ withCredentials: false,
61
+ visitDuration: 4 * 60, // default 4 hours
62
+ visitorDuration: 2 * 365 * 24 * 60 // default 2 years
23
63
  };
24
64
 
25
65
  var ahoy = window.ahoy || window.Ahoy || {};
26
66
 
27
67
  ahoy.configure = function (options) {
28
68
  for (var key in options) {
29
- if (options.hasOwnProperty(key)) {
69
+ if (Object.prototype.hasOwnProperty.call(options, key)) {
30
70
  config[key] = options[key];
31
71
  }
32
72
  }
@@ -37,8 +77,6 @@
37
77
 
38
78
  var $ = window.jQuery || window.Zepto || window.$;
39
79
  var visitId, visitorId, track;
40
- var visitTtl = 4 * 60; // 4 hours
41
- var visitorTtl = 2 * 365 * 24 * 60; // 2 years
42
80
  var isReady = false;
43
81
  var queue = [];
44
82
  var canStringify = typeof(JSON) !== "undefined" && typeof(JSON.stringify) !== "undefined";
@@ -52,46 +90,36 @@
52
90
  return config.urlPrefix + config.eventsUrl;
53
91
  }
54
92
 
93
+ function isEmpty(obj) {
94
+ return Object.keys(obj).length === 0;
95
+ }
96
+
55
97
  function canTrackNow() {
56
- return (config.useBeacon || config.trackNow) && canStringify && typeof(window.navigator.sendBeacon) !== "undefined";
98
+ return (config.useBeacon || config.trackNow) && isEmpty(config.headers) && canStringify && typeof(window.navigator.sendBeacon) !== "undefined" && !config.withCredentials;
99
+ }
100
+
101
+ function serialize(object) {
102
+ var data = new FormData();
103
+ for (var key in object) {
104
+ if (Object.prototype.hasOwnProperty.call(object, key)) {
105
+ data.append(key, object[key]);
106
+ }
107
+ }
108
+ return data;
57
109
  }
58
110
 
59
111
  // cookies
60
112
 
61
- // http://www.quirksmode.org/js/cookies.html
62
113
  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=/";
114
+ Cookies.set(name, value, ttl, config.cookieDomain || config.domain);
75
115
  }
76
116
 
77
117
  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;
118
+ return Cookies.get(name);
91
119
  }
92
120
 
93
121
  function destroyCookie(name) {
94
- setCookie(name, "", -1);
122
+ Cookies.set(name, "", -1);
95
123
  }
96
124
 
97
125
  function log(message) {
@@ -102,81 +130,150 @@
102
130
 
103
131
  function setReady() {
104
132
  var callback;
105
- while (callback = queue.shift()) {
133
+ while ((callback = queue.shift())) {
106
134
  callback();
107
135
  }
108
136
  isReady = true;
109
137
  }
110
138
 
111
- function ready(callback) {
139
+ ahoy.ready = function (callback) {
112
140
  if (isReady) {
113
141
  callback();
114
142
  } else {
115
143
  queue.push(callback);
116
144
  }
145
+ };
146
+
147
+ function matchesSelector(element, selector) {
148
+ var matches = element.matches ||
149
+ element.matchesSelector ||
150
+ element.mozMatchesSelector ||
151
+ element.msMatchesSelector ||
152
+ element.oMatchesSelector ||
153
+ element.webkitMatchesSelector;
154
+
155
+ if (matches) {
156
+ if (matches.apply(element, [selector])) {
157
+ return element;
158
+ } else if (element.parentElement) {
159
+ return matchesSelector(element.parentElement, selector);
160
+ }
161
+ return null;
162
+ } else {
163
+ log("Unable to match");
164
+ return null;
165
+ }
166
+ }
167
+
168
+ function onEvent(eventName, selector, callback) {
169
+ document.addEventListener(eventName, function (e) {
170
+ var matchedElement = matchesSelector(e.target, selector);
171
+ if (matchedElement) {
172
+ var skip = getClosest(matchedElement, "data-ahoy-skip");
173
+ if (skip !== null && skip !== "false") { return; }
174
+
175
+ callback.call(matchedElement, e);
176
+ }
177
+ });
117
178
  }
118
179
 
119
- // http://stackoverflow.com/a/2117523/1177228
180
+ // http://beeker.io/jquery-document-ready-equivalent-vanilla-javascript
181
+ function documentReady(callback) {
182
+ if (document.readyState === "interactive" || document.readyState === "complete") {
183
+ setTimeout(callback, 0);
184
+ } else {
185
+ document.addEventListener("DOMContentLoaded", callback);
186
+ }
187
+ }
188
+
189
+ // https://stackoverflow.com/a/2117523/1177228
120
190
  function generateId() {
121
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
122
- var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
191
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
192
+ var r = Math.random() * 16 | 0;
193
+ var v = c === 'x' ? r : (r & 0x3 | 0x8);
123
194
  return v.toString(16);
124
195
  });
125
196
  }
126
197
 
127
198
  function saveEventQueue() {
128
- // TODO add stringify method for IE 7 and under
129
- if (canStringify) {
199
+ if (config.cookies && canStringify) {
130
200
  setCookie("ahoy_events", JSON.stringify(eventQueue), 1);
131
201
  }
132
202
  }
133
203
 
134
- // from jquery-ujs
204
+ // from rails-ujs
135
205
 
136
206
  function csrfToken() {
137
- return $("meta[name=csrf-token]").attr("content");
207
+ var meta = document.querySelector("meta[name=csrf-token]");
208
+ return meta && meta.content;
138
209
  }
139
210
 
140
211
  function csrfParam() {
141
- return $("meta[name=csrf-param]").attr("content");
212
+ var meta = document.querySelector("meta[name=csrf-param]");
213
+ return meta && meta.content;
142
214
  }
143
215
 
144
216
  function CSRFProtection(xhr) {
145
217
  var token = csrfToken();
146
- if (token) xhr.setRequestHeader("X-CSRF-Token", token);
218
+ if (token) { xhr.setRequestHeader("X-CSRF-Token", token); }
147
219
  }
148
220
 
149
221
  function sendRequest(url, data, success) {
150
222
  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
- });
223
+ if ($ && $.ajax) {
224
+ $.ajax({
225
+ type: "POST",
226
+ url: url,
227
+ data: JSON.stringify(data),
228
+ contentType: "application/json; charset=utf-8",
229
+ dataType: "json",
230
+ beforeSend: CSRFProtection,
231
+ success: success,
232
+ headers: config.headers,
233
+ xhrFields: {
234
+ withCredentials: config.withCredentials
235
+ }
236
+ });
237
+ } else {
238
+ var xhr = new XMLHttpRequest();
239
+ xhr.open("POST", url, true);
240
+ xhr.withCredentials = config.withCredentials;
241
+ xhr.setRequestHeader("Content-Type", "application/json");
242
+ for (var header in config.headers) {
243
+ if (Object.prototype.hasOwnProperty.call(config.headers, header)) {
244
+ xhr.setRequestHeader(header, config.headers[header]);
245
+ }
246
+ }
247
+ xhr.onload = function () {
248
+ if (xhr.status === 200) {
249
+ success();
250
+ }
251
+ };
252
+ CSRFProtection(xhr);
253
+ xhr.send(JSON.stringify(data));
254
+ }
160
255
  }
161
256
  }
162
257
 
163
258
  function eventData(event) {
164
259
  var data = {
165
- events: [event],
166
- visit_token: event.visit_token,
167
- visitor_token: event.visitor_token
260
+ events: [event]
168
261
  };
262
+ if (config.cookies) {
263
+ data.visit_token = event.visit_token;
264
+ data.visitor_token = event.visitor_token;
265
+ }
169
266
  delete event.visit_token;
170
267
  delete event.visitor_token;
171
268
  return data;
172
269
  }
173
270
 
174
271
  function trackEvent(event) {
175
- ready( function () {
176
- sendRequest(eventsUrl(), eventData(event), function() {
272
+ ahoy.ready(function () {
273
+ sendRequest(eventsUrl(), eventData(event), function () {
177
274
  // remove from queue
178
275
  for (var i = 0; i < eventQueue.length; i++) {
179
- if (eventQueue[i].id == event.id) {
276
+ if (eventQueue[i].id === event.id) {
180
277
  eventQueue.splice(i, 1);
181
278
  break;
182
279
  }
@@ -187,13 +284,15 @@
187
284
  }
188
285
 
189
286
  function trackEventNow(event) {
190
- ready( function () {
287
+ ahoy.ready(function () {
191
288
  var data = eventData(event);
192
289
  var param = csrfParam();
193
290
  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);
291
+ if (param && token) { data[param] = token; }
292
+ // stringify so we keep the type
293
+ data.events_json = JSON.stringify(data.events);
294
+ delete data.events;
295
+ window.navigator.sendBeacon(eventsUrl(), serialize(data));
197
296
  });
198
297
  }
199
298
 
@@ -201,15 +300,39 @@
201
300
  return config.page || window.location.pathname;
202
301
  }
203
302
 
204
- 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"),
303
+ function presence(str) {
304
+ return (str && str.length > 0) ? str : null;
305
+ }
306
+
307
+ function cleanObject(obj) {
308
+ for (var key in obj) {
309
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
310
+ if (obj[key] === null) {
311
+ delete obj[key];
312
+ }
313
+ }
314
+ }
315
+ return obj;
316
+ }
317
+
318
+ function eventProperties() {
319
+ return cleanObject({
320
+ tag: this.tagName.toLowerCase(),
321
+ id: presence(this.id),
322
+ "class": presence(this.className),
210
323
  page: page(),
211
- section: $target.closest("*[data-section]").data("section")
212
- };
324
+ section: getClosest(this, "data-section")
325
+ });
326
+ }
327
+
328
+ function getClosest(element, attribute) {
329
+ for (; element && element !== document; element = element.parentNode) {
330
+ if (element.hasAttribute(attribute)) {
331
+ return element.getAttribute(attribute);
332
+ }
333
+ }
334
+
335
+ return null;
213
336
  }
214
337
 
215
338
  function createVisit() {
@@ -219,18 +342,17 @@
219
342
  visitorId = ahoy.getVisitorId();
220
343
  track = getCookie("ahoy_track");
221
344
 
222
- if (visitId && visitorId && !track) {
345
+ if (config.cookies === false || config.trackVisits === false) {
346
+ log("Visit tracking disabled");
347
+ setReady();
348
+ } else if (visitId && visitorId && !track) {
223
349
  // TODO keep visit alive?
224
350
  log("Active visit");
225
351
  setReady();
226
352
  } else {
227
- if (track) {
228
- destroyCookie("ahoy_track");
229
- }
230
-
231
353
  if (!visitId) {
232
354
  visitId = generateId();
233
- setCookie("ahoy_visit", visitId, visitTtl);
355
+ setCookie("ahoy_visit", visitId, config.visitDuration);
234
356
  }
235
357
 
236
358
  // make sure cookies are enabled
@@ -239,7 +361,7 @@
239
361
 
240
362
  if (!visitorId) {
241
363
  visitorId = generateId();
242
- setCookie("ahoy_visitor", visitorId, visitorTtl);
364
+ setCookie("ahoy_visitor", visitorId, config.visitorDuration);
243
365
  }
244
366
 
245
367
  var data = {
@@ -248,7 +370,8 @@
248
370
  platform: config.platform,
249
371
  landing_page: window.location.href,
250
372
  screen_width: window.screen.width,
251
- screen_height: window.screen.height
373
+ screen_height: window.screen.height,
374
+ js: true
252
375
  };
253
376
 
254
377
  // referrer
@@ -256,9 +379,19 @@
256
379
  data.referrer = document.referrer;
257
380
  }
258
381
 
382
+ for (var key in config.visitParams) {
383
+ if (Object.prototype.hasOwnProperty.call(config.visitParams, key)) {
384
+ data[key] = config.visitParams[key];
385
+ }
386
+ }
387
+
259
388
  log(data);
260
389
 
261
- sendRequest(visitsUrl(), data, setReady);
390
+ sendRequest(visitsUrl(), data, function () {
391
+ // wait until successful to destroy
392
+ destroyCookie("ahoy_track");
393
+ setReady();
394
+ });
262
395
  } else {
263
396
  log("Cookies disabled");
264
397
  setReady();
@@ -294,37 +427,39 @@
294
427
  ahoy.track = function (name, properties) {
295
428
  // generate unique id
296
429
  var event = {
297
- id: generateId(),
298
430
  name: name,
299
431
  properties: properties || {},
300
- time: (new Date()).getTime() / 1000.0
432
+ time: (new Date()).getTime() / 1000.0,
433
+ id: generateId(),
434
+ js: true
301
435
  };
302
436
 
303
- // wait for createVisit to log
304
- $( function () {
305
- log(event);
306
- });
307
-
308
- ready( function () {
309
- if (!ahoy.getVisitId()) {
437
+ ahoy.ready(function () {
438
+ if (config.cookies && !ahoy.getVisitId()) {
310
439
  createVisit();
311
440
  }
312
441
 
313
- event.visit_token = ahoy.getVisitId();
314
- event.visitor_token = ahoy.getVisitorId();
442
+ ahoy.ready(function () {
443
+ log(event);
315
444
 
316
- if (canTrackNow()) {
317
- trackEventNow(event);
318
- } else {
319
- eventQueue.push(event);
320
- saveEventQueue();
445
+ event.visit_token = ahoy.getVisitId();
446
+ event.visitor_token = ahoy.getVisitorId();
321
447
 
322
- // wait in case navigating to reduce duplicate events
323
- setTimeout( function () {
324
- trackEvent(event);
325
- }, 1000);
326
- }
448
+ if (canTrackNow()) {
449
+ trackEventNow(event);
450
+ } else {
451
+ eventQueue.push(event);
452
+ saveEventQueue();
453
+
454
+ // wait in case navigating to reduce duplicate events
455
+ setTimeout(function () {
456
+ trackEvent(event);
457
+ }, 1000);
458
+ }
459
+ });
327
460
  });
461
+
462
+ return true;
328
463
  };
329
464
 
330
465
  ahoy.trackView = function (additionalProperties) {
@@ -335,8 +470,8 @@
335
470
  };
336
471
 
337
472
  if (additionalProperties) {
338
- for(var propName in additionalProperties) {
339
- if (additionalProperties.hasOwnProperty(propName)) {
473
+ for (var propName in additionalProperties) {
474
+ if (Object.prototype.hasOwnProperty.call(additionalProperties, propName)) {
340
475
  properties[propName] = additionalProperties[propName];
341
476
  }
342
477
  }
@@ -344,37 +479,39 @@
344
479
  ahoy.track("$view", properties);
345
480
  };
346
481
 
347
- ahoy.trackClicks = function () {
348
- $(document).on("click", "a, button, input[type=submit]", function (e) {
349
- var $target = $(e.currentTarget);
350
- var properties = eventProperties(e);
351
- properties.text = properties.tag == "input" ? $target.val() : $.trim($target.text().replace(/[\s\r\n]+/g, " "));
352
- properties.href = $target.attr("href");
482
+ ahoy.trackClicks = function (selector) {
483
+ if (selector === undefined) {
484
+ throw new Error("Missing selector");
485
+ }
486
+ onEvent("click", selector, function (e) {
487
+ var properties = eventProperties.call(this, e);
488
+ properties.text = properties.tag === "input" ? this.value : (this.textContent || this.innerText || this.innerHTML).replace(/[\s\r\n]+/g, " ").trim();
489
+ properties.href = this.href;
353
490
  ahoy.track("$click", properties);
354
491
  });
355
492
  };
356
493
 
357
- ahoy.trackSubmits = function () {
358
- $(document).on("submit", "form", function (e) {
359
- var properties = eventProperties(e);
494
+ ahoy.trackSubmits = function (selector) {
495
+ if (selector === undefined) {
496
+ throw new Error("Missing selector");
497
+ }
498
+ onEvent("submit", selector, function (e) {
499
+ var properties = eventProperties.call(this, e);
360
500
  ahoy.track("$submit", properties);
361
501
  });
362
502
  };
363
503
 
364
- ahoy.trackChanges = function () {
365
- $(document).on("change", "input, textarea, select", function (e) {
366
- var properties = eventProperties(e);
504
+ ahoy.trackChanges = function (selector) {
505
+ log("trackChanges is deprecated and will be removed in 0.5.0");
506
+ if (selector === undefined) {
507
+ throw new Error("Missing selector");
508
+ }
509
+ onEvent("change", selector, function (e) {
510
+ var properties = eventProperties.call(this, e);
367
511
  ahoy.track("$change", properties);
368
512
  });
369
513
  };
370
514
 
371
- ahoy.trackAll = function() {
372
- ahoy.trackView();
373
- ahoy.trackClicks();
374
- ahoy.trackSubmits();
375
- ahoy.trackChanges();
376
- };
377
-
378
515
  // push events from queue
379
516
  try {
380
517
  eventQueue = JSON.parse(getCookie("ahoy_events") || "[]");
@@ -392,11 +529,12 @@
392
529
  ahoy.start = function () {};
393
530
  };
394
531
 
395
- $( function () {
532
+ documentReady(function () {
396
533
  if (config.startOnReady) {
397
534
  ahoy.start();
398
535
  }
399
536
  });
400
537
 
401
- window.ahoy = ahoy;
402
- }(window));
538
+ return ahoy;
539
+
540
+ }));