UNPKG

8.38 kBJavaScriptView Raw
1import {Class} from './Class';
2import * as Util from './Util';
3
4/*
5 * @class Evented
6 * @aka L.Evented
7 * @inherits Class
8 *
9 * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
10 *
11 * @example
12 *
13 * ```js
14 * map.on('click', function(e) {
15 * alert(e.latlng);
16 * } );
17 * ```
18 *
19 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
20 *
21 * ```js
22 * function onClick(e) { ... }
23 *
24 * map.on('click', onClick);
25 * map.off('click', onClick);
26 * ```
27 */
28
29export var Events = {
30 /* @method on(type: String, fn: Function, context?: Object): this
31 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
32 *
33 * @alternative
34 * @method on(eventMap: Object): this
35 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
36 */
37 on: function (types, fn, context) {
38
39 // types can be a map of types/handlers
40 if (typeof types === 'object') {
41 for (var type in types) {
42 // we don't process space-separated events here for performance;
43 // it's a hot path since Layer uses the on(obj) syntax
44 this._on(type, types[type], fn);
45 }
46
47 } else {
48 // types can be a string of space-separated words
49 types = Util.splitWords(types);
50
51 for (var i = 0, len = types.length; i < len; i++) {
52 this._on(types[i], fn, context);
53 }
54 }
55
56 return this;
57 },
58
59 /* @method off(type: String, fn?: Function, context?: Object): this
60 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
61 *
62 * @alternative
63 * @method off(eventMap: Object): this
64 * Removes a set of type/listener pairs.
65 *
66 * @alternative
67 * @method off: this
68 * Removes all listeners to all events on the object. This includes implicitly attached events.
69 */
70 off: function (types, fn, context) {
71
72 if (!types) {
73 // clear all listeners if called without arguments
74 delete this._events;
75
76 } else if (typeof types === 'object') {
77 for (var type in types) {
78 this._off(type, types[type], fn);
79 }
80
81 } else {
82 types = Util.splitWords(types);
83
84 for (var i = 0, len = types.length; i < len; i++) {
85 this._off(types[i], fn, context);
86 }
87 }
88
89 return this;
90 },
91
92 // attach listener (without syntactic sugar now)
93 _on: function (type, fn, context) {
94 this._events = this._events || {};
95
96 /* get/init listeners for type */
97 var typeListeners = this._events[type];
98 if (!typeListeners) {
99 typeListeners = [];
100 this._events[type] = typeListeners;
101 }
102
103 if (context === this) {
104 // Less memory footprint.
105 context = undefined;
106 }
107 var newListener = {fn: fn, ctx: context},
108 listeners = typeListeners;
109
110 // check if fn already there
111 for (var i = 0, len = listeners.length; i < len; i++) {
112 if (listeners[i].fn === fn && listeners[i].ctx === context) {
113 return;
114 }
115 }
116
117 listeners.push(newListener);
118 },
119
120 _off: function (type, fn, context) {
121 var listeners,
122 i,
123 len;
124
125 if (!this._events) { return; }
126
127 listeners = this._events[type];
128
129 if (!listeners) {
130 return;
131 }
132
133 if (!fn) {
134 // Set all removed listeners to noop so they are not called if remove happens in fire
135 for (i = 0, len = listeners.length; i < len; i++) {
136 listeners[i].fn = Util.falseFn;
137 }
138 // clear all listeners for a type if function isn't specified
139 delete this._events[type];
140 return;
141 }
142
143 if (context === this) {
144 context = undefined;
145 }
146
147 if (listeners) {
148
149 // find fn and remove it
150 for (i = 0, len = listeners.length; i < len; i++) {
151 var l = listeners[i];
152 if (l.ctx !== context) { continue; }
153 if (l.fn === fn) {
154
155 // set the removed listener to noop so that's not called if remove happens in fire
156 l.fn = Util.falseFn;
157
158 if (this._firingCount) {
159 /* copy array in case events are being fired */
160 this._events[type] = listeners = listeners.slice();
161 }
162 listeners.splice(i, 1);
163
164 return;
165 }
166 }
167 }
168 },
169
170 // @method fire(type: String, data?: Object, propagate?: Boolean): this
171 // Fires an event of the specified type. You can optionally provide an data
172 // object — the first argument of the listener function will contain its
173 // properties. The event can optionally be propagated to event parents.
174 fire: function (type, data, propagate) {
175 if (!this.listens(type, propagate)) { return this; }
176
177 var event = Util.extend({}, data, {
178 type: type,
179 target: this,
180 sourceTarget: data && data.sourceTarget || this
181 });
182
183 if (this._events) {
184 var listeners = this._events[type];
185
186 if (listeners) {
187 this._firingCount = (this._firingCount + 1) || 1;
188 for (var i = 0, len = listeners.length; i < len; i++) {
189 var l = listeners[i];
190 l.fn.call(l.ctx || this, event);
191 }
192
193 this._firingCount--;
194 }
195 }
196
197 if (propagate) {
198 // propagate the event to parents (set with addEventParent)
199 this._propagateEvent(event);
200 }
201
202 return this;
203 },
204
205 // @method listens(type: String): Boolean
206 // Returns `true` if a particular event type has any listeners attached to it.
207 listens: function (type, propagate) {
208 var listeners = this._events && this._events[type];
209 if (listeners && listeners.length) { return true; }
210
211 if (propagate) {
212 // also check parents for listeners if event propagates
213 for (var id in this._eventParents) {
214 if (this._eventParents[id].listens(type, propagate)) { return true; }
215 }
216 }
217 return false;
218 },
219
220 // @method once(…): this
221 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
222 once: function (types, fn, context) {
223
224 if (typeof types === 'object') {
225 for (var type in types) {
226 this.once(type, types[type], fn);
227 }
228 return this;
229 }
230
231 var handler = Util.bind(function () {
232 this
233 .off(types, fn, context)
234 .off(types, handler, context);
235 }, this);
236
237 // add a listener that's executed once and removed after that
238 return this
239 .on(types, fn, context)
240 .on(types, handler, context);
241 },
242
243 // @method addEventParent(obj: Evented): this
244 // Adds an event parent - an `Evented` that will receive propagated events
245 addEventParent: function (obj) {
246 this._eventParents = this._eventParents || {};
247 this._eventParents[Util.stamp(obj)] = obj;
248 return this;
249 },
250
251 // @method removeEventParent(obj: Evented): this
252 // Removes an event parent, so it will stop receiving propagated events
253 removeEventParent: function (obj) {
254 if (this._eventParents) {
255 delete this._eventParents[Util.stamp(obj)];
256 }
257 return this;
258 },
259
260 _propagateEvent: function (e) {
261 for (var id in this._eventParents) {
262 this._eventParents[id].fire(e.type, Util.extend({
263 layer: e.target,
264 propagatedFrom: e.target
265 }, e), true);
266 }
267 }
268};
269
270// aliases; we should ditch those eventually
271
272// @method addEventListener(…): this
273// Alias to [`on(…)`](#evented-on)
274Events.addEventListener = Events.on;
275
276// @method removeEventListener(…): this
277// Alias to [`off(…)`](#evented-off)
278
279// @method clearAllEventListeners(…): this
280// Alias to [`off()`](#evented-off)
281Events.removeEventListener = Events.clearAllEventListeners = Events.off;
282
283// @method addOneTimeEventListener(…): this
284// Alias to [`once(…)`](#evented-once)
285Events.addOneTimeEventListener = Events.once;
286
287// @method fireEvent(…): this
288// Alias to [`fire(…)`](#evented-fire)
289Events.fireEvent = Events.fire;
290
291// @method hasEventListeners(…): Boolean
292// Alias to [`listens(…)`](#evented-listens)
293Events.hasEventListeners = Events.listens;
294
295export var Evented = Class.extend(Events);