EventDispatcher.js
7.17 KB
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
/**
* EventDispatcher.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This class lets you add/remove and fire events by name on the specified scope. This makes
* it easy to add event listener logic to any class.
*
* @class tinymce.util.EventDispatcher
* @example
* var eventDispatcher = new EventDispatcher();
*
* eventDispatcher.on('click', function() {console.log('data');});
* eventDispatcher.fire('click', {data: 123});
*/
define("tinymce/util/EventDispatcher", [
"tinymce/util/Tools"
], function(Tools) {
var nativeEvents = Tools.makeMap(
"focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " +
"mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " +
"draggesture dragdrop drop drag submit " +
"compositionstart compositionend compositionupdate touchstart touchmove touchend",
' '
);
function Dispatcher(settings) {
var self = this, scope, bindings = {}, toggleEvent;
function returnFalse() {
return false;
}
function returnTrue() {
return true;
}
settings = settings || {};
scope = settings.scope || self;
toggleEvent = settings.toggleEvent || returnFalse;
/**
* Fires the specified event by name.
*
* @method fire
* @param {String} name Name of the event to fire.
* @param {Object?} args Event arguments.
* @return {Object} Event args instance passed in.
* @example
* instance.fire('event', {...});
*/
function fire(name, args) {
var handlers, i, l, callback;
name = name.toLowerCase();
args = args || {};
args.type = name;
// Setup target is there isn't one
if (!args.target) {
args.target = scope;
}
// Add event delegation methods if they are missing
if (!args.preventDefault) {
// Add preventDefault method
args.preventDefault = function() {
args.isDefaultPrevented = returnTrue;
};
// Add stopPropagation
args.stopPropagation = function() {
args.isPropagationStopped = returnTrue;
};
// Add stopImmediatePropagation
args.stopImmediatePropagation = function() {
args.isImmediatePropagationStopped = returnTrue;
};
// Add event delegation states
args.isDefaultPrevented = returnFalse;
args.isPropagationStopped = returnFalse;
args.isImmediatePropagationStopped = returnFalse;
}
if (settings.beforeFire) {
settings.beforeFire(args);
}
handlers = bindings[name];
if (handlers) {
for (i = 0, l = handlers.length; i < l; i++) {
callback = handlers[i];
// Unbind handlers marked with "once"
if (callback.once) {
off(name, callback.func);
}
// Stop immediate propagation if needed
if (args.isImmediatePropagationStopped()) {
args.stopPropagation();
return args;
}
// If callback returns false then prevent default and stop all propagation
if (callback.func.call(scope, args) === false) {
args.preventDefault();
return args;
}
}
}
return args;
}
/**
* Binds an event listener to a specific event by name.
*
* @method on
* @param {String} name Event name or space separated list of events to bind.
* @param {callback} callback Callback to be executed when the event occurs.
* @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
* @return {Object} Current class instance.
* @example
* instance.on('event', function(e) {
* // Callback logic
* });
*/
function on(name, callback, prepend, extra) {
var handlers, names, i;
if (callback === false) {
callback = returnFalse;
}
if (callback) {
callback = {
func: callback
};
if (extra) {
Tools.extend(callback, extra);
}
names = name.toLowerCase().split(' ');
i = names.length;
while (i--) {
name = names[i];
handlers = bindings[name];
if (!handlers) {
handlers = bindings[name] = [];
toggleEvent(name, true);
}
if (prepend) {
handlers.unshift(callback);
} else {
handlers.push(callback);
}
}
}
return self;
}
/**
* Unbinds an event listener to a specific event by name.
*
* @method off
* @param {String?} name Name of the event to unbind.
* @param {callback?} callback Callback to unbind.
* @return {Object} Current class instance.
* @example
* // Unbind specific callback
* instance.off('event', handler);
*
* // Unbind all listeners by name
* instance.off('event');
*
* // Unbind all events
* instance.off();
*/
function off(name, callback) {
var i, handlers, bindingName, names, hi;
if (name) {
names = name.toLowerCase().split(' ');
i = names.length;
while (i--) {
name = names[i];
handlers = bindings[name];
// Unbind all handlers
if (!name) {
for (bindingName in bindings) {
toggleEvent(bindingName, false);
delete bindings[bindingName];
}
return self;
}
if (handlers) {
// Unbind all by name
if (!callback) {
handlers.length = 0;
} else {
// Unbind specific ones
hi = handlers.length;
while (hi--) {
if (handlers[hi].func === callback) {
handlers = handlers.slice(0, hi).concat(handlers.slice(hi + 1));
bindings[name] = handlers;
}
}
}
if (!handlers.length) {
toggleEvent(name, false);
delete bindings[name];
}
}
}
} else {
for (name in bindings) {
toggleEvent(name, false);
}
bindings = {};
}
return self;
}
/**
* Binds an event listener to a specific event by name
* and automatically unbind the event once the callback fires.
*
* @method once
* @param {String} name Event name or space separated list of events to bind.
* @param {callback} callback Callback to be executed when the event occurs.
* @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
* @return {Object} Current class instance.
* @example
* instance.once('event', function(e) {
* // Callback logic
* });
*/
function once(name, callback, prepend) {
return on(name, callback, prepend, {once: true});
}
/**
* Returns true/false if the dispatcher has a event of the specified name.
*
* @method has
* @param {String} name Name of the event to check for.
* @return {Boolean} true/false if the event exists or not.
*/
function has(name) {
name = name.toLowerCase();
return !(!bindings[name] || bindings[name].length === 0);
}
// Expose
self.fire = fire;
self.on = on;
self.off = off;
self.once = once;
self.has = has;
}
/**
* Returns true/false if the specified event name is a native browser event or not.
*
* @method isNative
* @param {String} name Name to check if it's native.
* @return {Boolean} true/false if the event is native or not.
* @static
*/
Dispatcher.isNative = function(name) {
return !!nativeEvents[name.toLowerCase()];
};
return Dispatcher;
});