1 | import {Class} from './Class';
|
2 | import * 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 |
|
29 | export 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)
|
274 | Events.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)
|
281 | Events.removeEventListener = Events.clearAllEventListeners = Events.off;
|
282 |
|
283 | // @method addOneTimeEventListener(…): this
|
284 | // Alias to [`once(…)`](#evented-once)
|
285 | Events.addOneTimeEventListener = Events.once;
|
286 |
|
287 | // @method fireEvent(…): this
|
288 | // Alias to [`fire(…)`](#evented-fire)
|
289 | Events.fireEvent = Events.fire;
|
290 |
|
291 | // @method hasEventListeners(…): Boolean
|
292 | // Alias to [`listens(…)`](#evented-listens)
|
293 | Events.hasEventListeners = Events.listens;
|
294 |
|
295 | export var Evented = Class.extend(Events);
|