-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathevtify.js
158 lines (154 loc) · 4.89 KB
/
evtify.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
var type = require('./type');
var assert = require('./assert');
var extend = require('./extend');
var slice = Array.prototype.slice;
function isEmpty(o) {
if (o == null) {
return true;
}
if (type(o) === 'string') {
return o.trim().length === 0;
}
if (type(o) === 'array') {
return o.length === 0;
}
if (type(o) === 'object') {
return Object.keys(o).length === 0;
}
return false;
}
/**
* simple event handling system:
* {
* //event's format :'event.sub.subsub'
* on: function(event, callback, [context]){...},
* // when context is missing, removes all
* // callbacks with that callback function. If `callback` is null,
* // removes all callbacks for the event. If `event` is null, removes
* // all bound callbacks for all events. when off 'event'.
* off: function(event, [callback], [context]){...},
* once: function(event, callback, [context]){...},
* //when emit 'event:sub:subsub', any callbacks on this event path, like 'event' or 'event:sub' or "event:sub:subsub"'s callbacks will be invoked
* emit: function(event, [args...]){...}
* }
*
* one event hanlder is consists of a callback and a context, and it can only be identified by these two combined together.
* one event handler can only be registered once for a specific event , if there is a duplicate registration the handler is just
* ignored.
*/
/*-----------------------private-----------------------------*/
var eventSpliter = /\s+/;
var _makeEvtHandler = function(handler, context) {
assert(type(handler) === 'function', '[evtify]handler must be a function!');
return [handler, context];
};
var _invokeEvtHandler = function(evtHandler, args) {
evtHandler[0].apply(evtHandler[1], args);
};
//register handler for single event
var _registerHandler = function(eventMap, event, callback, context) {
eventMap[event] = eventMap[event] || [];
if (!eventMap[event].some(function(handler) {
return handler[0] === callback && handler[1] === context;
})) {
eventMap[event].push(_makeEvtHandler(callback, context));
}
};
var _removeHandler = function(eventMap, event, callback, context) {
if (isEmpty(eventMap[event])) {
return;
}
if (callback === undefined) {
delete eventMap[event];
return;
}
eventMap[event] = eventMap[event].filter(function(handler) {
return handler[0] !== callback || (context === void 0 ? false : handler[1] !== context);
});
if (isEmpty(eventMap[event])) {
delete eventMap[event];
}
};
var _emitHandler = function(eventMap, event, args) {
assert(/^(?:[\w\.]+)(?:\:[\w\.]+)*$/.test(event), "[evtify]The format of event str '" + event + "' is invalid!");
var paths = event.split(':'),
evtHandlers = [],
i = 0,
handlers;
while (i < paths.length) {
handlers = eventMap[paths.slice(0, ++i).join(':')];
if (!isEmpty(handlers)) evtHandlers.push(handlers);
}
evtHandlers.forEach(function(handlers) {
handlers.forEach(function(handler) {
_invokeEvtHandler(handler, args);
});
});
};
/*-----------------------public-----------------------------*/
var on = function(event, callback, context) {
assert(type(event) === 'string' && !isEmpty(event), '[evtify on]event must be a non empty string!');
assert(type(callback) === 'function', '[evtify on]handler must be a function!');
var events = event.trim().split(eventSpliter),
eventMap = this._eventMap = this._eventMap || {};
//default context is the eventified obj itself
context = context || this;
events.forEach(function(evt) {
_registerHandler(eventMap, evt, callback, context);
});
return this;
};
var once = function(event, callback, context) {
var self = this;
this.on(event, function onceCallback() {
callback.apply(this, arguments);
self.off(event, onceCallback, this);
}, context);
return this;
};
var off = function(event, callback, context) {
if (arguments.length === 0) {
delete this._eventMap;
return this;
}
if (isEmpty(this._eventMap) || type(event) !== 'string') {
return this;
}
var events = event.trim().split(eventSpliter),
eventMap = this._eventMap;
events.forEach(function(evt) {
_removeHandler(eventMap, evt, callback, context);
});
return this;
};
var emit = function(event) {
if (isEmpty(this._eventMap) || type(event) !== 'string') {
return this;
}
var events = event.trim().split(eventSpliter),
eventMap = this._eventMap,
args = slice.call(arguments, 1);
events.forEach(function(evt) {
_emitHandler(eventMap, evt, args);
});
return this;
};
var evtifyMixin = {
on: on,
once: once,
off: off,
emit: emit
};
function evtify(o) {
if (type(o) === 'object') {
return extend(o, evtifyMixin);
} else if (type(o) === 'function') {
var orgProto = o.prototype;
o.prototype = Object.create(evtifyMixin);
extend(o.prototype, orgProto);
return o;
} else {
throw new TypeError('[evtify] invalid target to be evtified!');
}
}
module.exports = evtify;