-
Notifications
You must be signed in to change notification settings - Fork 41
/
events.js
199 lines (161 loc) · 5.62 KB
/
events.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
// Events
// -----------------
// Thanks to:
// - https://github.com/documentcloud/backbone/blob/master/backbone.js
// - https://github.com/joyent/node/blob/master/lib/events.js
// Regular expression used to split event strings
var eventSplitter = /\s+/
// A module that can be mixed in to *any object* in order to provide it
// with custom events. You may bind with `on` or remove with `off` callback
// functions to an event; `trigger`-ing an event fires all callbacks in
// succession.
//
// var object = new Events();
// object.on('expand', function(){ alert('expanded'); });
// object.trigger('expand');
//
function Events() {
}
// Bind one or more space separated events, `events`, to a `callback`
// function. Passing `"all"` will bind the callback to all events fired.
Events.prototype.on = function(events, callback, context) {
var cache, event, list
if (!callback) return this
cache = this.__events || (this.__events = {})
events = events.split(eventSplitter)
while (event = events.shift()) {
list = cache[event] || (cache[event] = [])
list.push(callback, context)
}
return this
}
Events.prototype.once = function(events, callback, context) {
var that = this
var cb = function() {
that.off(events, cb)
callback.apply(context || that, arguments)
}
return this.on(events, cb, context)
}
// Remove one or many callbacks. If `context` is null, removes all callbacks
// with that function. If `callback` is null, removes all callbacks for the
// event. If `events` is null, removes all bound callbacks for all events.
Events.prototype.off = function(events, callback, context) {
var cache, event, list, i
// No events, or removing *all* events.
if (!(cache = this.__events)) return this
if (!(events || callback || context)) {
delete this.__events
return this
}
events = events ? events.split(eventSplitter) : keys(cache)
// Loop through the callback list, splicing where appropriate.
while (event = events.shift()) {
list = cache[event]
if (!list) continue
if (!(callback || context)) {
delete cache[event]
continue
}
for (i = list.length - 2; i >= 0; i -= 2) {
if (!(callback && list[i] !== callback ||
context && list[i + 1] !== context)) {
list.splice(i, 2)
}
}
}
return this
}
// Trigger one or many events, firing all bound callbacks. Callbacks are
// passed the same arguments as `trigger` is, apart from the event name
// (unless you're listening on `"all"`, which will cause your callback to
// receive the true name of the event as the first argument).
Events.prototype.trigger = function(events) {
var cache, event, all, list, i, len, rest = [], args, returned = true;
if (!(cache = this.__events)) return this
events = events.split(eventSplitter)
// Fill up `rest` with the callback arguments. Since we're only copying
// the tail of `arguments`, a loop is much faster than Array#slice.
for (i = 1, len = arguments.length; i < len; i++) {
rest[i - 1] = arguments[i]
}
// For each event, walk through the list of callbacks twice, first to
// trigger the event, then to trigger any `"all"` callbacks.
while (event = events.shift()) {
// Copy callback lists to prevent modification.
if (all = cache.all) all = all.slice()
if (list = cache[event]) list = list.slice()
// Execute event callbacks except one named "all"
if (event !== 'all') {
returned = triggerEvents(list, rest, this) && returned
}
// Execute "all" callbacks.
returned = triggerEvents(all, [event].concat(rest), this) && returned
}
return returned
}
Events.prototype.emit = Events.prototype.trigger
// Helpers
// -------
var keys = Object.keys
if (!keys) {
keys = function(o) {
var result = []
for (var name in o) {
if (o.hasOwnProperty(name)) {
result.push(name)
}
}
return result
}
}
// Mix `Events` to object instance or Class function.
Events.mixTo = function(receiver) {
var proto = Events.prototype
if (isFunction(receiver)) {
for (var key in proto) {
if (proto.hasOwnProperty(key)) {
receiver.prototype[key] = proto[key]
}
}
Object.keys(proto).forEach(function(key) {
receiver.prototype[key] = proto[key]
})
}
else {
var event = new Events
for (var key in proto) {
if (proto.hasOwnProperty(key)) {
copyProto(key)
}
}
}
function copyProto(key) {
receiver[key] = function() {
proto[key].apply(event, Array.prototype.slice.call(arguments))
return this
}
}
}
// Execute callbacks
function triggerEvents(list, args, context) {
var pass = true
if (list) {
var i = 0, l = list.length, a1 = args[0], a2 = args[1], a3 = args[2]
// call is faster than apply, optimize less than 3 argu
// http://blog.csdn.net/zhengyinhui100/article/details/7837127
switch (args.length) {
case 0: for (; i < l; i += 2) {pass = list[i].call(list[i + 1] || context) !== false && pass} break;
case 1: for (; i < l; i += 2) {pass = list[i].call(list[i + 1] || context, a1) !== false && pass} break;
case 2: for (; i < l; i += 2) {pass = list[i].call(list[i + 1] || context, a1, a2) !== false && pass} break;
case 3: for (; i < l; i += 2) {pass = list[i].call(list[i + 1] || context, a1, a2, a3) !== false && pass} break;
default: for (; i < l; i += 2) {pass = list[i].apply(list[i + 1] || context, args) !== false && pass} break;
}
}
// trigger will return false if one of the callbacks return false
return pass;
}
function isFunction(func) {
return Object.prototype.toString.call(func) === '[object Function]'
}
module.exports = Events