3 exports.__esModule = true;
4 exports.fixEvent = fixEvent;
7 exports.trigger = trigger;
10 var _dom = require('./dom.js');
12 var Dom = _interopRequireWildcard(_dom);
14 var _guid = require('./guid.js');
16 var Guid = _interopRequireWildcard(_guid);
18 var _log = require('./log.js');
20 var _log2 = _interopRequireDefault(_log);
22 var _window = require('global/window');
24 var _window2 = _interopRequireDefault(_window);
26 var _document = require('global/document');
28 var _document2 = _interopRequireDefault(_document);
30 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
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; } }
35 * Clean up the listener cache and dispatchers
37 * @param {Element|Object} elem
40 * @param {string} type
41 * Type of event to clean up
43 function _cleanUpEvents(elem, type) {
44 var data = Dom.getElData(elem);
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
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);
60 // Remove the events object if there are no types left
61 if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
63 delete data.dispatcher;
67 // Finally remove the element data if there is no data left
68 if (Object.getOwnPropertyNames(data).length === 0) {
69 Dom.removeElData(elem);
74 * Loops through an array of event types and calls the requested method for each type.
76 * @param {Function} fn
77 * The event method we want to use.
79 * @param {Element|Object} elem
80 * Element or object to bind listeners to
82 * @param {string} type
83 * Type of event to bind to.
85 * @param {EventTarget~EventListener} callback
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.
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);
105 * Fix a native event to have standard property values
107 * @param {Object} event
108 * Event object to fix.
111 * Fixed event object.
113 function fixEvent(event) {
115 function returnTrue() {
119 function returnFalse() {
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;
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];
150 // The event occurred on this element
152 event.target = event.srcElement || _document2['default'];
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;
160 // Stop the default browser action
161 event.preventDefault = function () {
162 if (old.preventDefault) {
163 old.preventDefault();
165 event.returnValue = false;
166 old.returnValue = false;
167 event.defaultPrevented = true;
170 event.defaultPrevented = false;
172 // Stop the event from bubbling
173 event.stopPropagation = function () {
174 if (old.stopPropagation) {
175 old.stopPropagation();
177 event.cancelBubble = true;
178 old.cancelBubble = true;
179 event.isPropagationStopped = returnTrue;
182 event.isPropagationStopped = returnFalse;
184 // Stop the event from bubbling and executing other handlers
185 event.stopImmediatePropagation = function () {
186 if (old.stopImmediatePropagation) {
187 old.stopImmediatePropagation();
189 event.isImmediatePropagationStopped = returnTrue;
190 event.stopPropagation();
193 event.isImmediatePropagationStopped = returnFalse;
195 // Handle mouse position
196 if (event.clientX !== null && event.clientX !== undefined) {
197 var doc = _document2['default'].documentElement;
198 var body = _document2['default'].body;
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);
204 // Handle key presses
205 event.which = event.charCode || event.keyCode;
207 // Fix button for mouse clicks:
208 // 0 == left; 1 == middle; 2 == right
209 if (event.button !== null && event.button !== undefined) {
211 // The following is disabled because it does not pass videojs-standard
214 event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
219 // Returns fixed-up instance
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.
229 * @param {Element|Object} elem
230 * Element or object to bind listeners to
232 * @param {string|string[]} type
233 * Type of event to bind to.
235 * @param {EventTarget~EventListener} fn
238 function on(elem, type, fn) {
239 if (Array.isArray(type)) {
240 return _handleMultipleEvents(on, elem, type, fn);
243 var data = Dom.getElData(elem);
245 // We need a place to store all our handler data
246 if (!data.handlers) {
250 if (!data.handlers[type]) {
251 data.handlers[type] = [];
255 fn.guid = Guid.newGUID();
258 data.handlers[type].push(fn);
260 if (!data.dispatcher) {
261 data.disabled = false;
263 data.dispatcher = function (event, hash) {
269 event = fixEvent(event);
271 var handlers = data.handlers[event.type];
274 // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
275 var handlersCopy = handlers.slice(0);
277 for (var m = 0, n = handlersCopy.length; m < n; m++) {
278 if (event.isImmediatePropagationStopped()) {
282 handlersCopy[m].call(elem, event, hash);
284 _log2['default'].error(e);
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);
302 * Removes event listeners from an element
304 * @param {Element|Object} elem
305 * Object to remove listeners from.
307 * @param {string|string[]} [type]
308 * Type of listener to remove. Don't include to remove all events from element.
310 * @param {EventTarget~EventListener} [fn]
311 * Specific listener to remove. Don't include to remove listeners for an event
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)) {
320 var data = Dom.getElData(elem);
322 // If no events exist, nothing to unbind
323 if (!data.handlers) {
327 if (Array.isArray(type)) {
328 return _handleMultipleEvents(off, elem, type, fn);
332 var removeType = function removeType(t) {
333 data.handlers[t] = [];
334 _cleanUpEvents(elem, t);
337 // Are we removing all bound events?
339 for (var t in data.handlers) {
345 var handlers = data.handlers[type];
347 // If no handlers exist, nothing to unbind
352 // If no listener was provided, remove all listeners for type
358 // We're only removing a single handler
360 for (var n = 0; n < handlers.length; n++) {
361 if (handlers[n].guid === fn.guid) {
362 handlers.splice(n--, 1);
367 _cleanUpEvents(elem, type);
371 * Trigger an event for an element
373 * @param {Element|Object} elem
374 * Element to trigger an event on
376 * @param {EventTarget~Event|string} event
377 * A string (the type) or an event object with a type attribute
379 * @param {Object} [hash]
380 * data hash to pass along with the event
382 * @return {boolean|undefined}
383 * - Returns the opposite of `defaultPrevented` if default was prevented
384 * - Otherwise returns undefined
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,
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 };
399 // Normalizes the event properties.
400 event = fixEvent(event);
402 // If the passed element has a dispatcher, executes the established handlers.
403 if (elemData.dispatcher) {
404 elemData.dispatcher.call(elem, event, hash);
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);
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);
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]();
424 // Re-enables event dispatching.
425 targetData.disabled = false;
429 // Inform the triggerer if the default was prevented by returning false
430 return !event.defaultPrevented;
434 * Trigger a listener only once for an event
436 * @param {Element|Object} elem
437 * Element or object to bind to.
439 * @param {string|string[]} type
442 * @param {Event~EventListener} fn
443 * Event Listener function
445 function one(elem, type, fn) {
446 if (Array.isArray(type)) {
447 return _handleMultipleEvents(one, elem, type, fn);
449 var func = function func() {
450 off(elem, type, func);
451 fn.apply(this, arguments);
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);