UNPKG

6.56 kBJavaScriptView Raw
1import * as util from './util';
2import * as is from './is';
3import Event from './event';
4
5const eventRegex = /^([^.]+)(\.(?:[^.]+))?$/; // regex for matching event strings (e.g. "click.namespace")
6const universalNamespace = '.*'; // matches as if no namespace specified and prevents users from unbinding accidentally
7
8const defaults = {
9 qualifierCompare: function( q1, q2 ){
10 return q1 === q2;
11 },
12 eventMatches: function( /*context, listener, eventObj*/ ){
13 return true;
14 },
15 addEventFields: function( /*context, evt*/ ){
16 },
17 callbackContext: function( context/*, listener, eventObj*/ ){
18 return context;
19 },
20 beforeEmit: function(/* context, listener, eventObj */){
21 },
22 afterEmit: function(/* context, listener, eventObj */){
23 },
24 bubble: function( /*context*/ ){
25 return false;
26 },
27 parent: function( /*context*/ ){
28 return null;
29 },
30 context: null
31};
32
33let defaultsKeys = Object.keys( defaults );
34let emptyOpts = {};
35
36function Emitter( opts = emptyOpts, context ){
37 // micro-optimisation vs Object.assign() -- reduces Element instantiation time
38 for( let i = 0; i < defaultsKeys.length; i++ ){
39 let key = defaultsKeys[i];
40
41 this[key] = opts[key] || defaults[key];
42 }
43
44 this.context = context || this.context;
45 this.listeners = [];
46 this.emitting = 0;
47}
48
49let p = Emitter.prototype;
50
51let forEachEvent = function( self, handler, events, qualifier, callback, conf, confOverrides ){
52 if( is.fn( qualifier ) ){
53 callback = qualifier;
54 qualifier = null;
55 }
56
57 if( confOverrides ){
58 if( conf == null ){
59 conf = confOverrides;
60 } else {
61 conf = util.assign( {}, conf, confOverrides );
62 }
63 }
64
65 let eventList = is.array(events) ? events : events.split(/\s+/);
66
67 for( let i = 0; i < eventList.length; i++ ){
68 let evt = eventList[i];
69
70 if( is.emptyString( evt ) ){ continue; }
71
72 let match = evt.match( eventRegex ); // type[.namespace]
73
74 if( match ){
75 let type = match[1];
76 let namespace = match[2] ? match[2] : null;
77 let ret = handler( self, evt, type, namespace, qualifier, callback, conf );
78
79 if( ret === false ){ break; } // allow exiting early
80 }
81 }
82};
83
84let makeEventObj = function( self, obj ){
85 self.addEventFields( self.context, obj );
86
87 return new Event( obj.type, obj );
88};
89
90let forEachEventObj = function( self, handler, events ){
91 if( is.event( events ) ){
92 handler( self, events );
93
94 return;
95 } else if( is.plainObject( events ) ){
96 handler( self, makeEventObj( self, events ) );
97
98 return;
99 }
100
101 let eventList = is.array(events) ? events : events.split(/\s+/);
102
103 for( let i = 0; i < eventList.length; i++ ){
104 let evt = eventList[i];
105
106 if( is.emptyString( evt ) ){ continue; }
107
108 let match = evt.match( eventRegex ); // type[.namespace]
109
110 if( match ){
111 let type = match[1];
112 let namespace = match[2] ? match[2] : null;
113 let eventObj = makeEventObj( self, {
114 type: type,
115 namespace: namespace,
116 target: self.context
117 } );
118
119 handler( self, eventObj );
120 }
121 }
122};
123
124p.on = p.addListener = function( events, qualifier, callback, conf, confOverrides ){
125 forEachEvent( this, function( self, event, type, namespace, qualifier, callback, conf ){
126 if( is.fn( callback ) ){
127 self.listeners.push( {
128 event: event, // full event string
129 callback: callback, // callback to run
130 type: type, // the event type (e.g. 'click')
131 namespace: namespace, // the event namespace (e.g. ".foo")
132 qualifier: qualifier, // a restriction on whether to match this emitter
133 conf: conf // additional configuration
134 } );
135 }
136 }, events, qualifier, callback, conf, confOverrides );
137
138 return this;
139};
140
141p.one = function( events, qualifier, callback, conf ){
142 return this.on( events, qualifier, callback, conf, { one: true } );
143};
144
145p.removeListener = p.off = function( events, qualifier, callback, conf ){
146 if( this.emitting !== 0 ){
147 this.listeners = util.copyArray( this.listeners );
148 }
149
150 let listeners = this.listeners;
151
152 for( let i = listeners.length - 1; i >= 0; i-- ){
153 let listener = listeners[i];
154
155 forEachEvent( this, function( self, event, type, namespace, qualifier, callback/*, conf*/ ){
156 if(
157 ( listener.type === type || events === '*' ) &&
158 ( (!namespace && listener.namespace !== '.*') || listener.namespace === namespace ) &&
159 ( !qualifier || self.qualifierCompare( listener.qualifier, qualifier ) ) &&
160 ( !callback || listener.callback === callback )
161 ){
162 listeners.splice( i, 1 );
163
164 return false;
165 }
166 }, events, qualifier, callback, conf );
167 }
168
169 return this;
170};
171
172p.removeAllListeners = function(){
173 return this.removeListener('*');
174};
175
176p.emit = p.trigger = function( events, extraParams, manualCallback ){
177 let listeners = this.listeners;
178 let numListenersBeforeEmit = listeners.length;
179
180 this.emitting++;
181
182 if( !is.array( extraParams ) ){
183 extraParams = [ extraParams ];
184 }
185
186 forEachEventObj( this, function( self, eventObj ){
187 if( manualCallback != null ){
188 listeners = [{
189 event: eventObj.event,
190 type: eventObj.type,
191 namespace: eventObj.namespace,
192 callback: manualCallback
193 }];
194
195 numListenersBeforeEmit = listeners.length;
196 }
197
198 for( let i = 0; i < numListenersBeforeEmit; i++ ){
199 let listener = listeners[i];
200
201 if(
202 ( listener.type === eventObj.type ) &&
203 ( !listener.namespace || listener.namespace === eventObj.namespace || listener.namespace === universalNamespace ) &&
204 ( self.eventMatches( self.context, listener, eventObj ) )
205 ){
206 let args = [ eventObj ];
207
208 if( extraParams != null ){
209 util.push( args, extraParams );
210 }
211
212 self.beforeEmit( self.context, listener, eventObj );
213
214 if( listener.conf && listener.conf.one ){
215 self.listeners = self.listeners.filter( l => l !== listener );
216 }
217
218 let context = self.callbackContext( self.context, listener, eventObj );
219 let ret = listener.callback.apply( context, args );
220
221 self.afterEmit( self.context, listener, eventObj );
222
223 if( ret === false ){
224 eventObj.stopPropagation();
225 eventObj.preventDefault();
226 }
227 } // if listener matches
228 } // for listener
229
230 if( self.bubble( self.context ) && !eventObj.isPropagationStopped() ){
231 self.parent( self.context ).emit( eventObj, extraParams );
232 }
233 }, events );
234
235 this.emitting--;
236
237 return this;
238};
239
240export default Emitter;