/
shotgun.js
180 lines (147 loc) · 5.54 KB
/
shotgun.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
/*
Name: shotgun.js
Author: John Newman
Date: 12/22/2011
License: MIT
Version: 1.5
Description: Smarter than your average pubsub library. Small and fast.
Contains trappable internal events and gives you lots of control over your subscriptions.
As of v1.5:
Contains an event-based abstraction of the try/catch block. You can now
decouple your error handling from your work flow, set multiple tries to a single catch,
and even indirectly call your try recursively through an events channel.
Unique keys are now shorter and SHOTGUN will run a test to make sure generated keys are actually unique.
Contains a new trappable event: tryError
Internal events you can trap:
newListener -> Fired any time a new subscription is made. Gives you an object containing the event, key, and action.
rmEvent -> Fired any time you remove an entire group of actions by event name. Gives you the name of the event removed.
rmListener -> Fired any time you unsubscribe a single action by key. Gives you the name of the event and the name of the key.
tryError -> Fired any time SHOTGUN.try() publishes an error.
*/
// ! We're almost there. Figure out a way to put catches underneath tries?
(function (context) {
"use strict";
var eventsObj = {}, keysUsed = {}, version = '1.5', sg;
// Function for generating random strings
function genUnique() {
var i, newStr = '', chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghiklmnopqrstuvwxyz';
for (i = 0; i < 24; i += 1) {
newStr += chars[Math.floor(Math.random() * chars.length)];
}
if (!keysUsed[newStr]) {
keysUsed[newStr] = true;
return newStr;
} else {
return genUnique();
}
}
// General publish function {event, key, args}
function publish(obj) {
var i, key, keylen, action, name = eventsObj[obj.event];
// If the event exists and we are passed a key...
if (name && name[obj.key]) {
if (typeof obj.key === 'string') {
obj.key = [obj.key];
}
keylen = obj.key.length;
for (i = 0; i < keylen; i += 1) {
key = name[obj.key[i]];
action = key.action;
// If there is a function associated with that key...
if (action) {
// Run the function.
action.apply(null, obj.args);
}
}
// If the event exists and we are not passed a key...
} else if (name && !name[obj.key]) {
// Loop through all the keys associated with the event. For each one...
for (i in name) {
if (Object.prototype.hasOwnProperty.call(name, i)) {
// We call the function.
name[i].action.apply(null, obj.args);
}
}
}
}
// General subscription function {event, key, action}
function subscribe(obj) {
var key = obj.key || genUnique();
eventsObj[obj.event] = eventsObj[obj.event] || {};
eventsObj[obj.event][key] = {"action" : obj.action};
publish({"event" : "newListener", "args" : [obj]});
return key;
}
// General unsubscribe function {event, key}
function unsubscribe(obj) {
var i;
if (!obj.key) {
if (eventsObj[obj.event]) {
// Free up all previously used keys in the event
for (i in eventsObj[obj.event]) {
if (Object.prototype.hasOwnProperty.call(eventsObj[obj.event], i)) {
delete keysUsed[i];
}
}
// Delete the event
delete eventsObj[obj.event];
publish({"event" : "rmEvent", "args" : [obj.event]});
return true;
} else {
return false;
}
}
if (eventsObj[obj.event][obj.key]) {
// Delete the event
delete eventsObj[obj.event][obj.key];
// Free up the used key
delete keysUsed[obj.key];
publish({"event" : "rmListener", "args" : [obj.event, obj.key]});
return true;
} else {
return false;
}
}
// Try/catch abstraction function {event, key, action}
function attempt(obj) {
var key = obj.key || null;
try {
obj.action();
} catch (err) {
publish({"event" : obj.event, "key" : key, "args" : [err]});
if (obj.event !== 'tryError') {
publish({"event" : "tryError", "key" : key, "args" : [err]});
}
}
}
sg = {
"fire" : function (obj) {
return publish(obj);
},
"listen" : function (obj) {
return subscribe(obj);
},
"remove" : function (obj) {
return unsubscribe(obj);
},
"events" : function () {
return eventsObj;
},
"try" : function (obj) {
return attempt(obj);
},
"version" : version
};
// exports to multiple environments
// AMD
if (context.define && typeof context.define === 'function' && context.define.amd) {
context.define('SHOTGUN', [], sg);
//node
} else if (context.module && context.module.exports) {
context.module.exports = sg;
// browser
} else {
// use string because of Google closure compiler ADVANCED_MODE
context['SHOTGUN'] = context['SG'] = sg;
}
}(this));