1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | function Delegate(root) {
|
23 | 'use strict';
|
24 | var self = this;
|
25 |
|
26 | if (root) {
|
27 | this.root(root);
|
28 | }
|
29 |
|
30 |
|
31 | |
32 |
|
33 |
|
34 |
|
35 |
|
36 | this.listenerMap = {};
|
37 |
|
38 |
|
39 |
|
40 | this.handle = function(event) { Delegate.prototype.handle.call(self, event); };
|
41 | }
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 | Delegate.tagsCaseSensitive = null;
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | Delegate.prototype.root = function(root) {
|
56 | 'use strict';
|
57 | var listenerMap = this.listenerMap;
|
58 | var eventType;
|
59 |
|
60 | if (typeof root === 'string') {
|
61 | root = document.querySelector(root);
|
62 | }
|
63 |
|
64 |
|
65 | if (this.rootElement) {
|
66 | for (eventType in listenerMap) {
|
67 | if (listenerMap.hasOwnProperty(eventType)) {
|
68 | this.rootElement.removeEventListener(eventType, this.handle, this.captureForType(eventType));
|
69 | }
|
70 | }
|
71 | }
|
72 |
|
73 |
|
74 |
|
75 | if (!root || !root.addEventListener) {
|
76 | if (this.rootElement) {
|
77 | delete this.rootElement;
|
78 | }
|
79 | return;
|
80 | }
|
81 |
|
82 | |
83 |
|
84 |
|
85 |
|
86 |
|
87 | this.rootElement = root;
|
88 |
|
89 |
|
90 | for (eventType in listenerMap) {
|
91 | if (listenerMap.hasOwnProperty(eventType)) {
|
92 | this.rootElement.addEventListener(eventType, this.handle, this.captureForType(eventType));
|
93 | }
|
94 | }
|
95 | };
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 | Delegate.prototype.captureForType = function(eventType) {
|
103 | 'use strict';
|
104 | return eventType === 'error';
|
105 | };
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 | Delegate.prototype.on = function(eventType, selector, handler, eventData) {
|
122 | 'use strict';
|
123 | var root, listenerMap, matcher, matcherParam, self = this, SEPARATOR = ' ';
|
124 |
|
125 | if (!eventType) {
|
126 | throw new TypeError('Invalid event type: ' + eventType);
|
127 | }
|
128 |
|
129 | if (!selector) {
|
130 | throw new TypeError('Invalid selector: ' + selector);
|
131 | }
|
132 |
|
133 |
|
134 | if (eventType.indexOf(SEPARATOR) !== -1) {
|
135 | eventType.split(SEPARATOR).forEach(function(singleEventType) {
|
136 | self.on(singleEventType, selector, handler, eventData);
|
137 | });
|
138 |
|
139 | return this;
|
140 | }
|
141 |
|
142 |
|
143 | if (eventData === undefined) {
|
144 | eventData = null;
|
145 | }
|
146 |
|
147 | if (typeof handler !== 'function') {
|
148 | throw new TypeError('Handler must be a type of Function');
|
149 | }
|
150 |
|
151 | root = this.rootElement;
|
152 | listenerMap = this.listenerMap;
|
153 |
|
154 |
|
155 | if (!listenerMap[eventType]) {
|
156 | if (root) {
|
157 | root.addEventListener(eventType, this.handle, this.captureForType(eventType));
|
158 | }
|
159 | listenerMap[eventType] = [];
|
160 | }
|
161 |
|
162 |
|
163 | if (/^[a-z]+$/i.test(selector)) {
|
164 |
|
165 |
|
166 | if (Delegate.tagsCaseSensitive === null) {
|
167 | Delegate.tagsCaseSensitive = document.createElement('i').tagName === 'i';
|
168 | }
|
169 |
|
170 | if (!Delegate.tagsCaseSensitive) {
|
171 | matcherParam = selector.toUpperCase();
|
172 | } else {
|
173 | matcherParam = selector;
|
174 | }
|
175 |
|
176 | matcher = this.matchesTag;
|
177 | } else if (/^#[a-z0-9\-_]+$/i.test(selector)) {
|
178 | matcherParam = selector.slice(1);
|
179 | matcher = this.matchesId;
|
180 | } else {
|
181 | matcherParam = selector;
|
182 | matcher = this.matches;
|
183 | }
|
184 |
|
185 |
|
186 | listenerMap[eventType].push({
|
187 | selector: selector,
|
188 | eventData: eventData,
|
189 | handler: handler,
|
190 | matcher: matcher,
|
191 | matcherParam: matcherParam
|
192 | });
|
193 |
|
194 | return this;
|
195 | };
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 | Delegate.prototype.off = function(eventType, selector, handler) {
|
207 | 'use strict';
|
208 | var i, listener, listenerMap, listenerList, singleEventType, self = this, SEPARATOR = ' ';
|
209 |
|
210 | listenerMap = this.listenerMap;
|
211 | if (!eventType) {
|
212 | for (singleEventType in listenerMap) {
|
213 | if (listenerMap.hasOwnProperty(singleEventType)) {
|
214 | this.off(singleEventType, selector, handler);
|
215 | }
|
216 | }
|
217 |
|
218 | return this;
|
219 | }
|
220 |
|
221 | listenerList = listenerMap[eventType];
|
222 | if (!listenerList || !listenerList.length) {
|
223 | return this;
|
224 | }
|
225 |
|
226 |
|
227 | if (eventType.indexOf(SEPARATOR) !== -1) {
|
228 | eventType.split(SEPARATOR).forEach(function(singleEventType) {
|
229 | self.off(singleEventType, selector, handler);
|
230 | });
|
231 |
|
232 | return this;
|
233 | }
|
234 |
|
235 |
|
236 | for (i = listenerList.length - 1; i >= 0; i--) {
|
237 | listener = listenerList[i];
|
238 |
|
239 | if ((!selector || selector === listener.selector) && (!handler || handler === listener.handler)) {
|
240 | listenerList.splice(i, 1);
|
241 | }
|
242 | }
|
243 |
|
244 |
|
245 | if (!listenerList.length) {
|
246 | delete listenerList[eventType];
|
247 |
|
248 |
|
249 | if (this.rootElement) {
|
250 | this.rootElement.removeEventListener(eventType, this.handle, this.captureForType(eventType));
|
251 | }
|
252 | }
|
253 |
|
254 | return this;
|
255 | };
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 | Delegate.prototype.handle = function(event) {
|
264 | 'use strict';
|
265 | var i, l, root, listener, returned, listenerList, target, EVENTIGNORE = 'ftLabsDelegateIgnore';
|
266 |
|
267 | if (event[EVENTIGNORE] === true) {
|
268 | return;
|
269 | }
|
270 |
|
271 | target = event.target;
|
272 | if (target.nodeType === Node.TEXT_NODE) {
|
273 | target = target.parentNode;
|
274 | }
|
275 |
|
276 | root = this.rootElement;
|
277 | listenerList = this.listenerMap[event.type];
|
278 |
|
279 |
|
280 | l = listenerList.length;
|
281 | while (target && l) {
|
282 | for (i = 0; i < l; i++) {
|
283 | listener = listenerList[i];
|
284 |
|
285 |
|
286 | if (!listener) {
|
287 | break;
|
288 | }
|
289 |
|
290 |
|
291 |
|
292 | if (listener.matcher.call(target, listener.matcherParam, target)) {
|
293 | returned = this.fire(event, target, listener);
|
294 | }
|
295 |
|
296 |
|
297 | if (returned === false) {
|
298 | event[EVENTIGNORE] = true;
|
299 | return;
|
300 | }
|
301 | }
|
302 |
|
303 |
|
304 |
|
305 | if (target === root) {
|
306 | break;
|
307 | }
|
308 |
|
309 | l = listenerList.length;
|
310 | target = target.parentElement;
|
311 | }
|
312 | };
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 | Delegate.prototype.fire = function(event, target, listener) {
|
324 | 'use strict';
|
325 | var returned, oldData;
|
326 |
|
327 | if (listener.eventData !== null) {
|
328 | oldData = event.data;
|
329 | event.data = listener.eventData;
|
330 | returned = listener.handler.call(target, event, target);
|
331 | event.data = oldData;
|
332 | } else {
|
333 | returned = listener.handler.call(target, event, target);
|
334 | }
|
335 |
|
336 | return returned;
|
337 | };
|
338 |
|
339 |
|
340 |
|
341 |
|
342 |
|
343 |
|
344 |
|
345 |
|
346 | Delegate.prototype.matches = (function(p) {
|
347 | 'use strict';
|
348 | return (p.matchesSelector || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || p.oMatchesSelector);
|
349 | }(HTMLElement.prototype));
|
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 | Delegate.prototype.matchesTag = function(tagName, element) {
|
362 | 'use strict';
|
363 | return tagName === element.tagName;
|
364 | };
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 | Delegate.prototype.matchesId = function(id, element) {
|
377 | 'use strict';
|
378 | return id === element.id;
|
379 | };
|
380 |
|
381 | if (typeof define !== 'undefined' && define.amd) {
|
382 |
|
383 |
|
384 | define(function() {
|
385 | 'use strict';
|
386 | return Delegate;
|
387 | });
|
388 | }
|
389 |
|
390 | if (typeof module !== 'undefined' && module.exports) {
|
391 | module.exports = function(root) {
|
392 | 'use strict';
|
393 | return new Delegate(root);
|
394 | };
|
395 |
|
396 | module.exports.Delegate = Delegate;
|
397 | }
|
398 |
|
399 |
|
400 |
|
401 |
|
402 |
|
403 |
|
404 |
|
405 | Delegate.prototype.destroy = function() {
|
406 | this.off();
|
407 | this.root();
|
408 | };
|