Version 1
[yaffs-website] / node_modules / video.js / es5 / utils / events.js
1 'use strict';
2
3 exports.__esModule = true;
4 exports.fixEvent = fixEvent;
5 exports.on = on;
6 exports.off = off;
7 exports.trigger = trigger;
8 exports.one = one;
9
10 var _dom = require('./dom.js');
11
12 var Dom = _interopRequireWildcard(_dom);
13
14 var _guid = require('./guid.js');
15
16 var Guid = _interopRequireWildcard(_guid);
17
18 var _log = require('./log.js');
19
20 var _log2 = _interopRequireDefault(_log);
21
22 var _window = require('global/window');
23
24 var _window2 = _interopRequireDefault(_window);
25
26 var _document = require('global/document');
27
28 var _document2 = _interopRequireDefault(_document);
29
30 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
31
32 function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
33
34 /**
35  * Clean up the listener cache and dispatchers
36  *
37  * @param {Element|Object} elem
38  *        Element to clean up
39  *
40  * @param {string} type
41  *        Type of event to clean up
42  */
43 function _cleanUpEvents(elem, type) {
44   var data = Dom.getElData(elem);
45
46   // Remove the events of a particular type if there are none left
47   if (data.handlers[type].length === 0) {
48     delete data.handlers[type];
49     // data.handlers[type] = null;
50     // Setting to null was causing an error with data.handlers
51
52     // Remove the meta-handler from the element
53     if (elem.removeEventListener) {
54       elem.removeEventListener(type, data.dispatcher, false);
55     } else if (elem.detachEvent) {
56       elem.detachEvent('on' + type, data.dispatcher);
57     }
58   }
59
60   // Remove the events object if there are no types left
61   if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
62     delete data.handlers;
63     delete data.dispatcher;
64     delete data.disabled;
65   }
66
67   // Finally remove the element data if there is no data left
68   if (Object.getOwnPropertyNames(data).length === 0) {
69     Dom.removeElData(elem);
70   }
71 }
72
73 /**
74  * Loops through an array of event types and calls the requested method for each type.
75  *
76  * @param {Function} fn
77  *        The event method we want to use.
78  *
79  * @param {Element|Object} elem
80  *        Element or object to bind listeners to
81  *
82  * @param {string} type
83  *        Type of event to bind to.
84  *
85  * @param {EventTarget~EventListener} callback
86  *        Event listener.
87  */
88 /**
89  * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
90  * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
91  * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
92  * robust as jquery's, so there's probably some differences.
93  *
94  * @module events
95  */
96
97 function _handleMultipleEvents(fn, elem, types, callback) {
98   types.forEach(function (type) {
99     // Call the event method for each one of the types
100     fn(elem, type, callback);
101   });
102 }
103
104 /**
105  * Fix a native event to have standard property values
106  *
107  * @param {Object} event
108  *        Event object to fix.
109  *
110  * @return {Object}
111  *         Fixed event object.
112  */
113 function fixEvent(event) {
114
115   function returnTrue() {
116     return true;
117   }
118
119   function returnFalse() {
120     return false;
121   }
122
123   // Test if fixing up is needed
124   // Used to check if !event.stopPropagation instead of isPropagationStopped
125   // But native events return true for stopPropagation, but don't have
126   // other expected methods like isPropagationStopped. Seems to be a problem
127   // with the Javascript Ninja code. So we're just overriding all events now.
128   if (!event || !event.isPropagationStopped) {
129     var old = event || _window2['default'].event;
130
131     event = {};
132     // Clone the old object so that we can modify the values event = {};
133     // IE8 Doesn't like when you mess with native event properties
134     // Firefox returns false for event.hasOwnProperty('type') and other props
135     //  which makes copying more difficult.
136     // TODO: Probably best to create a whitelist of event props
137     for (var key in old) {
138       // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
139       // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
140       // and webkitMovementX/Y
141       if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') {
142         // Chrome 32+ warns if you try to copy deprecated returnValue, but
143         // we still want to if preventDefault isn't supported (IE8).
144         if (!(key === 'returnValue' && old.preventDefault)) {
145           event[key] = old[key];
146         }
147       }
148     }
149
150     // The event occurred on this element
151     if (!event.target) {
152       event.target = event.srcElement || _document2['default'];
153     }
154
155     // Handle which other element the event is related to
156     if (!event.relatedTarget) {
157       event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
158     }
159
160     // Stop the default browser action
161     event.preventDefault = function () {
162       if (old.preventDefault) {
163         old.preventDefault();
164       }
165       event.returnValue = false;
166       old.returnValue = false;
167       event.defaultPrevented = true;
168     };
169
170     event.defaultPrevented = false;
171
172     // Stop the event from bubbling
173     event.stopPropagation = function () {
174       if (old.stopPropagation) {
175         old.stopPropagation();
176       }
177       event.cancelBubble = true;
178       old.cancelBubble = true;
179       event.isPropagationStopped = returnTrue;
180     };
181
182     event.isPropagationStopped = returnFalse;
183
184     // Stop the event from bubbling and executing other handlers
185     event.stopImmediatePropagation = function () {
186       if (old.stopImmediatePropagation) {
187         old.stopImmediatePropagation();
188       }
189       event.isImmediatePropagationStopped = returnTrue;
190       event.stopPropagation();
191     };
192
193     event.isImmediatePropagationStopped = returnFalse;
194
195     // Handle mouse position
196     if (event.clientX !== null && event.clientX !== undefined) {
197       var doc = _document2['default'].documentElement;
198       var body = _document2['default'].body;
199
200       event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
201       event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
202     }
203
204     // Handle key presses
205     event.which = event.charCode || event.keyCode;
206
207     // Fix button for mouse clicks:
208     // 0 == left; 1 == middle; 2 == right
209     if (event.button !== null && event.button !== undefined) {
210
211       // The following is disabled because it does not pass videojs-standard
212       // and... yikes.
213       /* eslint-disable */
214       event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
215       /* eslint-enable */
216     }
217   }
218
219   // Returns fixed-up instance
220   return event;
221 }
222
223 /**
224  * Add an event listener to element
225  * It stores the handler function in a separate cache object
226  * and adds a generic handler to the element's event,
227  * along with a unique id (guid) to the element.
228  *
229  * @param {Element|Object} elem
230  *        Element or object to bind listeners to
231  *
232  * @param {string|string[]} type
233  *        Type of event to bind to.
234  *
235  * @param {EventTarget~EventListener} fn
236  *        Event listener.
237  */
238 function on(elem, type, fn) {
239   if (Array.isArray(type)) {
240     return _handleMultipleEvents(on, elem, type, fn);
241   }
242
243   var data = Dom.getElData(elem);
244
245   // We need a place to store all our handler data
246   if (!data.handlers) {
247     data.handlers = {};
248   }
249
250   if (!data.handlers[type]) {
251     data.handlers[type] = [];
252   }
253
254   if (!fn.guid) {
255     fn.guid = Guid.newGUID();
256   }
257
258   data.handlers[type].push(fn);
259
260   if (!data.dispatcher) {
261     data.disabled = false;
262
263     data.dispatcher = function (event, hash) {
264
265       if (data.disabled) {
266         return;
267       }
268
269       event = fixEvent(event);
270
271       var handlers = data.handlers[event.type];
272
273       if (handlers) {
274         // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
275         var handlersCopy = handlers.slice(0);
276
277         for (var m = 0, n = handlersCopy.length; m < n; m++) {
278           if (event.isImmediatePropagationStopped()) {
279             break;
280           } else {
281             try {
282               handlersCopy[m].call(elem, event, hash);
283             } catch (e) {
284               _log2['default'].error(e);
285             }
286           }
287         }
288       }
289     };
290   }
291
292   if (data.handlers[type].length === 1) {
293     if (elem.addEventListener) {
294       elem.addEventListener(type, data.dispatcher, false);
295     } else if (elem.attachEvent) {
296       elem.attachEvent('on' + type, data.dispatcher);
297     }
298   }
299 }
300
301 /**
302  * Removes event listeners from an element
303  *
304  * @param {Element|Object} elem
305  *        Object to remove listeners from.
306  *
307  * @param {string|string[]} [type]
308  *        Type of listener to remove. Don't include to remove all events from element.
309  *
310  * @param {EventTarget~EventListener} [fn]
311  *        Specific listener to remove. Don't include to remove listeners for an event
312  *        type.
313  */
314 function off(elem, type, fn) {
315   // Don't want to add a cache object through getElData if not needed
316   if (!Dom.hasElData(elem)) {
317     return;
318   }
319
320   var data = Dom.getElData(elem);
321
322   // If no events exist, nothing to unbind
323   if (!data.handlers) {
324     return;
325   }
326
327   if (Array.isArray(type)) {
328     return _handleMultipleEvents(off, elem, type, fn);
329   }
330
331   // Utility function
332   var removeType = function removeType(t) {
333     data.handlers[t] = [];
334     _cleanUpEvents(elem, t);
335   };
336
337   // Are we removing all bound events?
338   if (!type) {
339     for (var t in data.handlers) {
340       removeType(t);
341     }
342     return;
343   }
344
345   var handlers = data.handlers[type];
346
347   // If no handlers exist, nothing to unbind
348   if (!handlers) {
349     return;
350   }
351
352   // If no listener was provided, remove all listeners for type
353   if (!fn) {
354     removeType(type);
355     return;
356   }
357
358   // We're only removing a single handler
359   if (fn.guid) {
360     for (var n = 0; n < handlers.length; n++) {
361       if (handlers[n].guid === fn.guid) {
362         handlers.splice(n--, 1);
363       }
364     }
365   }
366
367   _cleanUpEvents(elem, type);
368 }
369
370 /**
371  * Trigger an event for an element
372  *
373  * @param {Element|Object} elem
374  *        Element to trigger an event on
375  *
376  * @param {EventTarget~Event|string} event
377  *        A string (the type) or an event object with a type attribute
378  *
379  * @param {Object} [hash]
380  *        data hash to pass along with the event
381  *
382  * @return {boolean|undefined}
383  *         - Returns the opposite of `defaultPrevented` if default was prevented
384  *         - Otherwise returns undefined
385  */
386 function trigger(elem, event, hash) {
387   // Fetches element data and a reference to the parent (for bubbling).
388   // Don't want to add a data object to cache for every parent,
389   // so checking hasElData first.
390   var elemData = Dom.hasElData(elem) ? Dom.getElData(elem) : {};
391   var parent = elem.parentNode || elem.ownerDocument;
392   // type = event.type || event,
393   // handler;
394
395   // If an event name was passed as a string, creates an event out of it
396   if (typeof event === 'string') {
397     event = { type: event, target: elem };
398   }
399   // Normalizes the event properties.
400   event = fixEvent(event);
401
402   // If the passed element has a dispatcher, executes the established handlers.
403   if (elemData.dispatcher) {
404     elemData.dispatcher.call(elem, event, hash);
405   }
406
407   // Unless explicitly stopped or the event does not bubble (e.g. media events)
408   // recursively calls this function to bubble the event up the DOM.
409   if (parent && !event.isPropagationStopped() && event.bubbles === true) {
410     trigger.call(null, parent, event, hash);
411
412     // If at the top of the DOM, triggers the default action unless disabled.
413   } else if (!parent && !event.defaultPrevented) {
414     var targetData = Dom.getElData(event.target);
415
416     // Checks if the target has a default action for this event.
417     if (event.target[event.type]) {
418       // Temporarily disables event dispatching on the target as we have already executed the handler.
419       targetData.disabled = true;
420       // Executes the default action.
421       if (typeof event.target[event.type] === 'function') {
422         event.target[event.type]();
423       }
424       // Re-enables event dispatching.
425       targetData.disabled = false;
426     }
427   }
428
429   // Inform the triggerer if the default was prevented by returning false
430   return !event.defaultPrevented;
431 }
432
433 /**
434  * Trigger a listener only once for an event
435  *
436  * @param {Element|Object} elem
437  *        Element or object to bind to.
438  *
439  * @param {string|string[]} type
440  *        Name/type of event
441  *
442  * @param {Event~EventListener} fn
443  *        Event Listener function
444  */
445 function one(elem, type, fn) {
446   if (Array.isArray(type)) {
447     return _handleMultipleEvents(one, elem, type, fn);
448   }
449   var func = function func() {
450     off(elem, type, func);
451     fn.apply(this, arguments);
452   };
453
454   // copy the guid to the new function so it can removed using the original function's ID
455   func.guid = fn.guid = fn.guid || Guid.newGUID();
456   on(elem, type, func);
457 }