backbone-mixpanel 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.md +20 -0
- data/README.md +136 -0
- data/Rakefile +1 -0
- data/backbone-mixpanel.gemspec +22 -0
- data/lib/backbone-mixpanel.rb +5 -0
- data/lib/backbone-mixpanel/engine.rb +7 -0
- data/lib/backbone-mixpanel/version.rb +9 -0
- data/spec/SpecRunner.html +52 -0
- data/spec/lib/jasmine-1.3.1/MIT.LICENSE +20 -0
- data/spec/lib/jasmine-1.3.1/jasmine-html.js +681 -0
- data/spec/lib/jasmine-1.3.1/jasmine.css +82 -0
- data/spec/lib/jasmine-1.3.1/jasmine.js +2600 -0
- data/spec/lib/javascripts/backbone.js +1498 -0
- data/spec/lib/javascripts/jquery.js +9597 -0
- data/spec/lib/javascripts/underscore.js +1226 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/vendor/assets/javascripts/backbone-mixpanel_spec.js +232 -0
- data/vendor/assets/javascripts/backbone-mixpanel.js +105 -0
- metadata +109 -0
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
|
12
|
+
# Run specs in random order to surface order dependencies. If you find an
|
13
|
+
# order dependency and want to debug it, you can fix the order by providing
|
14
|
+
# the seed, which is printed after each run.
|
15
|
+
# --seed 1234
|
16
|
+
config.order = 'random'
|
17
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
describe("Backbone.Mixpanel", function () {
|
2
|
+
var bbm, options;
|
3
|
+
|
4
|
+
var initialize = function() {
|
5
|
+
bbm = Backbone.Mixpanel.init(options);
|
6
|
+
};
|
7
|
+
|
8
|
+
beforeEach(function () {
|
9
|
+
window.mixpanel = jasmine.createSpyObj("mixpanel", ["init", "track", "register", "name_tag"]);
|
10
|
+
|
11
|
+
options = { token: "abc123", customData: ['id', 'desc']};
|
12
|
+
});
|
13
|
+
|
14
|
+
describe(".initialize", function () {
|
15
|
+
beforeEach(function () {
|
16
|
+
options = {
|
17
|
+
token: "abc123",
|
18
|
+
userInfo: { id: 45, paid: true },
|
19
|
+
nameTag: "Foo-Bar"
|
20
|
+
};
|
21
|
+
|
22
|
+
initialize();
|
23
|
+
});
|
24
|
+
|
25
|
+
it("should initialize the mixpanel js", function () {
|
26
|
+
expect(mixpanel.init).toHaveBeenCalledWith("abc123");
|
27
|
+
});
|
28
|
+
|
29
|
+
it("should set the user information", function () {
|
30
|
+
expect(mixpanel.register).toHaveBeenCalledWith({ id: 45, paid: true });
|
31
|
+
});
|
32
|
+
|
33
|
+
it("should set a name tag", function () {
|
34
|
+
expect(mixpanel.name_tag).toHaveBeenCalledWith("Foo-Bar");
|
35
|
+
});
|
36
|
+
|
37
|
+
describe("when mixpanel is not defined", function () {
|
38
|
+
beforeEach(function () {
|
39
|
+
window.mixpanel = undefined;
|
40
|
+
});
|
41
|
+
|
42
|
+
it("should not error", function () {
|
43
|
+
expect(function() { initialize() }).not.toThrow();
|
44
|
+
});
|
45
|
+
});
|
46
|
+
|
47
|
+
describe("when not given a token", function () {
|
48
|
+
var message;
|
49
|
+
|
50
|
+
beforeEach(function () {
|
51
|
+
message = "Backbone.Mixpanel.init requires the mixpanel token from your account.";
|
52
|
+
|
53
|
+
delete options.token;
|
54
|
+
});
|
55
|
+
|
56
|
+
it("should error", function () {
|
57
|
+
expect(function() { initialize() }).toThrow(message);
|
58
|
+
});
|
59
|
+
});
|
60
|
+
});
|
61
|
+
|
62
|
+
describe("#wrapEvent", function () {
|
63
|
+
var func, wrapped, event;
|
64
|
+
|
65
|
+
beforeEach(function () {
|
66
|
+
initialize();
|
67
|
+
|
68
|
+
spyOn(bbm, "trackEvent");
|
69
|
+
func = jasmine.createSpy("Function");
|
70
|
+
|
71
|
+
wrapped = bbm.wrapEvent(func, "the default value");
|
72
|
+
event = jasmine.createSpy("Event");
|
73
|
+
});
|
74
|
+
|
75
|
+
describe("when not given a function", function () {
|
76
|
+
it("should error", function () {
|
77
|
+
expect(function() { bbm.wrapEvent() }).toThrow("Wrapping requires a function to wrap");
|
78
|
+
});
|
79
|
+
});
|
80
|
+
|
81
|
+
describe("when not given a default value", function () {
|
82
|
+
it("should error", function () {
|
83
|
+
expect(function() { bbm.wrapEvent(func) }).toThrow("Wrapping requires a default description");
|
84
|
+
});
|
85
|
+
});
|
86
|
+
|
87
|
+
it("should return a function that wraps the original function", function () {
|
88
|
+
wrapped({});
|
89
|
+
expect(func).toHaveBeenCalled();
|
90
|
+
});
|
91
|
+
|
92
|
+
it("should track the event", function () {
|
93
|
+
wrapped({});
|
94
|
+
expect(bbm.trackEvent).toHaveBeenCalled();
|
95
|
+
});
|
96
|
+
|
97
|
+
describe("for the description", function () {
|
98
|
+
describe("when the event's target has a data attribute", function () {
|
99
|
+
beforeEach(function () {
|
100
|
+
event.currentTarget = '<div data-event="Foo Event :)"></div>';
|
101
|
+
|
102
|
+
wrapped(event);
|
103
|
+
});
|
104
|
+
|
105
|
+
it("should log the event text", function () {
|
106
|
+
expect(bbm.trackEvent).toHaveBeenCalledWith("Foo Event :)", {});
|
107
|
+
});
|
108
|
+
});
|
109
|
+
|
110
|
+
describe("when the event's target does not have a data attribute", function () {
|
111
|
+
beforeEach(function () {
|
112
|
+
event.currentTarget = '<div data-invalid=":/"></div>';
|
113
|
+
|
114
|
+
wrapped(event);
|
115
|
+
});
|
116
|
+
|
117
|
+
it("should log the default description", function () {
|
118
|
+
expect(bbm.trackEvent).toHaveBeenCalledWith("the default value", {});
|
119
|
+
});
|
120
|
+
});
|
121
|
+
});
|
122
|
+
|
123
|
+
describe("for the additional data", function () {
|
124
|
+
describe("when the event's target has a metadata attribute", function () {
|
125
|
+
beforeEach(function () {
|
126
|
+
event.currentTarget = '<div data-id="THE ID"></div>';
|
127
|
+
|
128
|
+
wrapped(event);
|
129
|
+
});
|
130
|
+
|
131
|
+
it("should log custom id", function () {
|
132
|
+
expect(bbm.trackEvent.mostRecentCall.args[1].id).toEqual("THE ID");
|
133
|
+
});
|
134
|
+
|
135
|
+
describe("when the event's target has multiple metadata attributes", function () {
|
136
|
+
beforeEach(function () {
|
137
|
+
event.currentTarget = '<div data-id="12" data-desc="A Description!"></div>';
|
138
|
+
|
139
|
+
wrapped(event);
|
140
|
+
});
|
141
|
+
|
142
|
+
it("should log custom attributes", function () {
|
143
|
+
expect(bbm.trackEvent.mostRecentCall.args[1].id).toEqual(12);
|
144
|
+
expect(bbm.trackEvent.mostRecentCall.args[1].desc).toEqual("A Description!");
|
145
|
+
});
|
146
|
+
});
|
147
|
+
});
|
148
|
+
});
|
149
|
+
});
|
150
|
+
|
151
|
+
describe("#trackEvent", function () {
|
152
|
+
beforeEach(function () {
|
153
|
+
initialize();
|
154
|
+
});
|
155
|
+
|
156
|
+
it("should track the given string", function () {
|
157
|
+
bbm.trackEvent("Some Text");
|
158
|
+
|
159
|
+
expect(mixpanel.track).toHaveBeenCalledWith("Some Text", {});
|
160
|
+
});
|
161
|
+
|
162
|
+
it("should track the default extra information", function () {
|
163
|
+
bbm.trackEvent(null);
|
164
|
+
|
165
|
+
expect(mixpanel.track).toHaveBeenCalledWith(null, {});
|
166
|
+
});
|
167
|
+
|
168
|
+
it("should track the extra information", function () {
|
169
|
+
bbm.trackEvent(null, {extra: 'info', key: 'value'});
|
170
|
+
|
171
|
+
expect(mixpanel.track).toHaveBeenCalledWith(null, {extra: 'info', key: 'value'});
|
172
|
+
});
|
173
|
+
});
|
174
|
+
|
175
|
+
describe("#delegateEvents", function () {
|
176
|
+
var view,
|
177
|
+
delegate = function() {
|
178
|
+
bbm.delegateEvents.call(view);
|
179
|
+
};
|
180
|
+
|
181
|
+
beforeEach(function () {
|
182
|
+
initialize();
|
183
|
+
});
|
184
|
+
|
185
|
+
describe("when the view has no events", function () {
|
186
|
+
beforeEach(function () {
|
187
|
+
view = {};
|
188
|
+
});
|
189
|
+
|
190
|
+
it("should not error", function () {
|
191
|
+
expect(function() { delegate() }).not.toThrow();
|
192
|
+
});
|
193
|
+
});
|
194
|
+
|
195
|
+
describe("when the view has an events hash", function () {
|
196
|
+
beforeEach(function () {
|
197
|
+
view = {
|
198
|
+
$el: { on: function() {} },
|
199
|
+
undelegateEvents: function() {},
|
200
|
+
events: {
|
201
|
+
"click .some-class": "clickSomeClass"
|
202
|
+
}
|
203
|
+
};
|
204
|
+
});
|
205
|
+
|
206
|
+
it("should error (no method on the view)", function () {
|
207
|
+
expect(function() { delegate() }).toThrow("Method \"clickSomeClass\" does not exist");
|
208
|
+
});
|
209
|
+
|
210
|
+
describe("when the method exists", function () {
|
211
|
+
var method;
|
212
|
+
|
213
|
+
beforeEach(function () {
|
214
|
+
spyOn(bbm, 'wrapEvent').andCallThrough();
|
215
|
+
|
216
|
+
method = function() { return "hey" };
|
217
|
+
view.clickSomeClass = method;
|
218
|
+
});
|
219
|
+
|
220
|
+
it("should not error", function () {
|
221
|
+
expect(function() { delegate() }).not.toThrow();
|
222
|
+
});
|
223
|
+
|
224
|
+
it("should wrap the error", function () {
|
225
|
+
delegate();
|
226
|
+
|
227
|
+
expect(bbm.wrapEvent).toHaveBeenCalledWith(method);
|
228
|
+
});
|
229
|
+
});
|
230
|
+
});
|
231
|
+
});
|
232
|
+
});
|
@@ -0,0 +1,105 @@
|
|
1
|
+
Backbone.View.originalDelegateEvents = Backbone.View.prototype.delegateEvents;
|
2
|
+
|
3
|
+
Backbone.Mixpanel = (function() {
|
4
|
+
var self = {},
|
5
|
+
defaultOptions = {
|
6
|
+
token: null,
|
7
|
+
enabled: false,
|
8
|
+
eventDataAttr: 'event',
|
9
|
+
customData: [],
|
10
|
+
userInfo: {},
|
11
|
+
nameTag: ''
|
12
|
+
},
|
13
|
+
options = {};
|
14
|
+
|
15
|
+
// setup the Mixpanel environment
|
16
|
+
self.init = function(opts) {
|
17
|
+
_.extend(options, defaultOptions, opts || {});
|
18
|
+
|
19
|
+
if(!options.token) {
|
20
|
+
throw new Error("Backbone.Mixpanel.init requires the mixpanel token from your account.");
|
21
|
+
}
|
22
|
+
|
23
|
+
initialize();
|
24
|
+
|
25
|
+
return self;
|
26
|
+
};
|
27
|
+
|
28
|
+
// Returns a function that tracks to mixpanel and calls the original method
|
29
|
+
// method - (required) The method to handle an DOM event after tracking
|
30
|
+
self.wrapEvent = function(method, defaultDescription) {
|
31
|
+
if(!_.isFunction(method)) throw new Error("Wrapping requires a function to wrap");
|
32
|
+
if(!defaultDescription) throw new Error("Wrapping requires a default description");
|
33
|
+
|
34
|
+
return function(event) {
|
35
|
+
var data = {},
|
36
|
+
$target = $(event.currentTarget),
|
37
|
+
description = $target.data(options.eventDataAttr) || defaultDescription;
|
38
|
+
|
39
|
+
_(options.customData).each(function(key) {
|
40
|
+
var item = $target.data(key);
|
41
|
+
|
42
|
+
if(item) data[key] = item;
|
43
|
+
});
|
44
|
+
|
45
|
+
self.trackEvent(description, data);
|
46
|
+
|
47
|
+
method.apply(this, arguments);
|
48
|
+
}
|
49
|
+
};
|
50
|
+
|
51
|
+
// Track events to mixpanel when enabled and mixpanel is available
|
52
|
+
// desc - (required) The description of the event
|
53
|
+
// data - (optional) Any additional data included in the event
|
54
|
+
self.trackEvent = function(desc, data) {
|
55
|
+
data || (data = {});
|
56
|
+
|
57
|
+
// where the magic happens.
|
58
|
+
mixpanel.track(desc, data);
|
59
|
+
};
|
60
|
+
|
61
|
+
self.delegateEvents = function(events) {
|
62
|
+
if (!(events || (events = _.result(this, 'events')))) return;
|
63
|
+
|
64
|
+
for(var key in events) {
|
65
|
+
var method = events[key];
|
66
|
+
if(!_.isFunction(method)) method = this[method];
|
67
|
+
if(!method) throw new Error('Method "' + events[key] + '" does not exist');
|
68
|
+
|
69
|
+
var wr = self.wrapEvent(method, key);
|
70
|
+
events[key] = wr;
|
71
|
+
|
72
|
+
Backbone.View.originalDelegateEvents.call(this, events);
|
73
|
+
}
|
74
|
+
};
|
75
|
+
|
76
|
+
// Replace the delegateEvents function so that we can wrap each of the event
|
77
|
+
// handlers given via the events hash/function
|
78
|
+
Backbone.View.prototype.delegateEvents = self.delegateEvents;
|
79
|
+
|
80
|
+
return self;
|
81
|
+
|
82
|
+
// Setup the Mixpanel Javascript and initialize it with the Mixpanel token.
|
83
|
+
function initialize() {
|
84
|
+
window.mixpanel || (window.mixpanel = []);
|
85
|
+
|
86
|
+
var noop = function() {};
|
87
|
+
|
88
|
+
// Register the functions that we use internally to do nothing
|
89
|
+
if(!options.enabled) {
|
90
|
+
_.defaults(window.mixpanel, { init: noop, register: noop, name_tag: noop, track: noop });
|
91
|
+
}
|
92
|
+
|
93
|
+
if(options.enabled) loadMixpanel();
|
94
|
+
|
95
|
+
mixpanel.init(options.token);
|
96
|
+
mixpanel.register(options.userInfo);
|
97
|
+
mixpanel.name_tag(options.nameTag);
|
98
|
+
}
|
99
|
+
|
100
|
+
function loadMixpanel() {
|
101
|
+
(function(c,a){
|
102
|
+
window.mixpanel=a;var b,d,h,e;b=c.createElement('script');b.type='text/javascript';b.async=!0;b.src=('https:'===c.location.protocol?'https:':'http:')+'//cdn.mxpnl.com/libs/mixpanel-2.2.min.js';d=c.getElementsByTagName('script')[0];d.parentNode.insertBefore(b,d);a._i=[];a.init=function(b,c,f){function d(a,b){var c=b.split('.');2==c.length&&(a=a[c[0]],b=c[1]);a[b]=function(){a.push([b].concat(Array.prototype.slice.call(arguments,0)))}}var g=a;'undefined'!==typeof f?g=a[f]=[]:f='mixpanel';g.people=g.people||[];h=['disable','track','track_pageview','track_links','track_forms','register','register_once','unregister','identify','alias','name_tag','set_config','people.set','people.increment','people.track_charge','people.append'];for(e=0;e<h.length;e++)d(g,h[e]);a._i.push([b,c,f])};a.__SV=1.2;
|
103
|
+
})(document,window.mixpanel);
|
104
|
+
}
|
105
|
+
}());
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: backbone-mixpanel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Brian Norton
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: ''
|
47
|
+
email:
|
48
|
+
- brian.nort@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- .rspec
|
55
|
+
- Gemfile
|
56
|
+
- LICENSE.md
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- backbone-mixpanel.gemspec
|
60
|
+
- lib/backbone-mixpanel.rb
|
61
|
+
- lib/backbone-mixpanel/engine.rb
|
62
|
+
- lib/backbone-mixpanel/version.rb
|
63
|
+
- spec/SpecRunner.html
|
64
|
+
- spec/lib/jasmine-1.3.1/MIT.LICENSE
|
65
|
+
- spec/lib/jasmine-1.3.1/jasmine-html.js
|
66
|
+
- spec/lib/jasmine-1.3.1/jasmine.css
|
67
|
+
- spec/lib/jasmine-1.3.1/jasmine.js
|
68
|
+
- spec/lib/javascripts/backbone.js
|
69
|
+
- spec/lib/javascripts/jquery.js
|
70
|
+
- spec/lib/javascripts/underscore.js
|
71
|
+
- spec/spec_helper.rb
|
72
|
+
- spec/vendor/assets/javascripts/backbone-mixpanel_spec.js
|
73
|
+
- vendor/assets/javascripts/backbone-mixpanel.js
|
74
|
+
homepage: ''
|
75
|
+
licenses: []
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ! '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 1.8.25
|
95
|
+
signing_key:
|
96
|
+
specification_version: 3
|
97
|
+
summary: ''
|
98
|
+
test_files:
|
99
|
+
- spec/SpecRunner.html
|
100
|
+
- spec/lib/jasmine-1.3.1/MIT.LICENSE
|
101
|
+
- spec/lib/jasmine-1.3.1/jasmine-html.js
|
102
|
+
- spec/lib/jasmine-1.3.1/jasmine.css
|
103
|
+
- spec/lib/jasmine-1.3.1/jasmine.js
|
104
|
+
- spec/lib/javascripts/backbone.js
|
105
|
+
- spec/lib/javascripts/jquery.js
|
106
|
+
- spec/lib/javascripts/underscore.js
|
107
|
+
- spec/spec_helper.rb
|
108
|
+
- spec/vendor/assets/javascripts/backbone-mixpanel_spec.js
|
109
|
+
has_rdoc:
|