mirror of
				https://github.com/videojs/video.js.git
				synced 2025-10-31 00:08:01 +02:00 
			
		
		
		
	@misteroneill exposed DOM helpers. closes #2754
This commit is contained in:
		
				
					committed by
					
						 Gary Katsevman
						Gary Katsevman
					
				
			
			
				
	
			
			
			
						parent
						
							cd800b02ea
						
					
				
				
					commit
					f2fa8f8687
				
			| @@ -10,6 +10,7 @@ CHANGELOG | ||||
| * @paladox updated grunt-cli dependency ([view](https://github.com/videojs/video.js/pull/2555)) | ||||
| * @paladox updated grunt-contrib-jshint ([view](https://github.com/videojs/video.js/pull/2554)) | ||||
| * @siebrand updated dutch translations ([view](https://github.com/videojs/video.js/pull/2556)) | ||||
| * @misteroneill exposed DOM helpers ([view](https://github.com/videojs/video.js/pull/2754)) | ||||
|  | ||||
| -------------------- | ||||
|  | ||||
|   | ||||
| @@ -830,6 +830,46 @@ class Component { | ||||
|     }, 1); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Finds a single DOM element matching `selector` within the component's | ||||
|    * `contentEl` or another custom context. | ||||
|    * | ||||
|    * @method $ | ||||
|    * @param  {String} selector | ||||
|    *         A valid CSS selector, which will be passed to `querySelector`. | ||||
|    * | ||||
|    * @param  {Element|String} [context=document] | ||||
|    *         A DOM element within which to query. Can also be a selector | ||||
|    *         string in which case the first matching element will be used | ||||
|    *         as context. If missing (or no element matches selector), falls | ||||
|    *         back to `document`. | ||||
|    * | ||||
|    * @return {Element|null} | ||||
|    */ | ||||
|   $(selector, context) { | ||||
|     return Dom.$(selector, context || this.contentEl()); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Finds a all DOM elements matching `selector` within the component's | ||||
|    * `contentEl` or another custom context. | ||||
|    * | ||||
|    * @method $$ | ||||
|    * @param  {String} selector | ||||
|    *         A valid CSS selector, which will be passed to `querySelectorAll`. | ||||
|    * | ||||
|    * @param  {Element|String} [context=document] | ||||
|    *         A DOM element within which to query. Can also be a selector | ||||
|    *         string in which case the first matching element will be used | ||||
|    *         as context. If missing (or no element matches selector), falls | ||||
|    *         back to `document`. | ||||
|    * | ||||
|    * @return {NodeList} | ||||
|    */ | ||||
|   $$(selector, context) { | ||||
|     return Dom.$$(selector, context || this.contentEl()); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Check if a component's element has a CSS class name | ||||
|    * | ||||
| @@ -854,7 +894,7 @@ class Component { | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Remove and return a CSS class name from the component's element | ||||
|    * Remove a CSS class name from the component's element | ||||
|    * | ||||
|    * @param {String} classToRemove Classname to remove | ||||
|    * @return {Component} | ||||
| @@ -865,6 +905,23 @@ class Component { | ||||
|     return this; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Add or remove a CSS class name from the component's element | ||||
|    * | ||||
|    * @param  {String} classToToggle | ||||
|    * @param  {Boolean|Function} [predicate] | ||||
|    *         Can be a function that returns a Boolean. If `true`, the class | ||||
|    *         will be added; if `false`, the class will be removed. If not | ||||
|    *         given, the class will be added if not present and vice versa. | ||||
|    * | ||||
|    * @return {Component} | ||||
|    * @method toggleClass | ||||
|    */ | ||||
|   toggleClass(classToToggle, predicate) { | ||||
|     Dom.toggleElClass(this.el_, classToToggle, predicate); | ||||
|     return this; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Show the component element if hidden | ||||
|    * | ||||
|   | ||||
| @@ -281,8 +281,8 @@ class Player extends Component { | ||||
|     // of the player in a way that's still overrideable by CSS, just like the | ||||
|     // video element | ||||
|     this.styleEl_ = stylesheet.createStyleElement('vjs-styles-dimensions'); | ||||
|     let defaultsStyleEl = document.querySelector('.vjs-styles-defaults'); | ||||
|     let head = document.querySelector('head'); | ||||
|     let defaultsStyleEl = Dom.$('.vjs-styles-defaults'); | ||||
|     let head = Dom.$('head'); | ||||
|     head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild); | ||||
|  | ||||
|     // Pass in the width/height/aspectRatio options which will update the style el | ||||
|   | ||||
| @@ -771,7 +771,7 @@ class Html5 extends Tech { | ||||
|  | ||||
|     this.remoteTextTracks().removeTrack_(track); | ||||
|  | ||||
|     tracks = this.el().querySelectorAll('track'); | ||||
|     tracks = this.$$('track'); | ||||
|  | ||||
|     i = tracks.length; | ||||
|     while (i--) { | ||||
|   | ||||
| @@ -44,6 +44,8 @@ let trackToJson_ = function(track) { | ||||
|  * @function textTracksToJson | ||||
|  */ | ||||
| let textTracksToJson = function(tech) { | ||||
|  | ||||
|   // Cannot use $$ here because it is not an instance of Tech | ||||
|   let trackEls = tech.el().querySelectorAll('track'); | ||||
|  | ||||
|   let trackObjs = Array.prototype.map.call(trackEls, (t) => t.track); | ||||
|   | ||||
| @@ -27,33 +27,33 @@ class TextTrackSettings extends Component { | ||||
|       this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings; | ||||
|     } | ||||
|  | ||||
|     Events.on(this.el().querySelector('.vjs-done-button'), 'click', Fn.bind(this, function() { | ||||
|     Events.on(this.$('.vjs-done-button'), 'click', Fn.bind(this, function() { | ||||
|       this.saveSettings(); | ||||
|       this.hide(); | ||||
|     })); | ||||
|  | ||||
|     Events.on(this.el().querySelector('.vjs-default-button'), 'click', Fn.bind(this, function() { | ||||
|       this.el().querySelector('.vjs-fg-color > select').selectedIndex = 0; | ||||
|       this.el().querySelector('.vjs-bg-color > select').selectedIndex = 0; | ||||
|       this.el().querySelector('.window-color > select').selectedIndex = 0; | ||||
|       this.el().querySelector('.vjs-text-opacity > select').selectedIndex = 0; | ||||
|       this.el().querySelector('.vjs-bg-opacity > select').selectedIndex = 0; | ||||
|       this.el().querySelector('.vjs-window-opacity > select').selectedIndex = 0; | ||||
|       this.el().querySelector('.vjs-edge-style select').selectedIndex = 0; | ||||
|       this.el().querySelector('.vjs-font-family select').selectedIndex = 0; | ||||
|       this.el().querySelector('.vjs-font-percent select').selectedIndex = 2; | ||||
|     Events.on(this.$('.vjs-default-button'), 'click', Fn.bind(this, function() { | ||||
|       this.$('.vjs-fg-color > select').selectedIndex = 0; | ||||
|       this.$('.vjs-bg-color > select').selectedIndex = 0; | ||||
|       this.$('.window-color > select').selectedIndex = 0; | ||||
|       this.$('.vjs-text-opacity > select').selectedIndex = 0; | ||||
|       this.$('.vjs-bg-opacity > select').selectedIndex = 0; | ||||
|       this.$('.vjs-window-opacity > select').selectedIndex = 0; | ||||
|       this.$('.vjs-edge-style select').selectedIndex = 0; | ||||
|       this.$('.vjs-font-family select').selectedIndex = 0; | ||||
|       this.$('.vjs-font-percent select').selectedIndex = 2; | ||||
|       this.updateDisplay(); | ||||
|     })); | ||||
|  | ||||
|     Events.on(this.el().querySelector('.vjs-fg-color > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.el().querySelector('.vjs-bg-color > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.el().querySelector('.window-color > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.el().querySelector('.vjs-text-opacity > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.el().querySelector('.vjs-bg-opacity > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.el().querySelector('.vjs-window-opacity > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.el().querySelector('.vjs-font-percent select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.el().querySelector('.vjs-edge-style select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.el().querySelector('.vjs-font-family select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-fg-color > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-bg-color > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.window-color > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-text-opacity > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-bg-opacity > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-window-opacity > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-font-percent select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-edge-style select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-font-family select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|  | ||||
|     if (this.options_.persistTextTrackSettings) { | ||||
|       this.restoreSettings(); | ||||
| @@ -74,7 +74,7 @@ class TextTrackSettings extends Component { | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get texttrack settings  | ||||
|    * Get texttrack settings | ||||
|    * Settings are | ||||
|    * .vjs-edge-style | ||||
|    * .vjs-font-family | ||||
| @@ -83,23 +83,21 @@ class TextTrackSettings extends Component { | ||||
|    * .vjs-bg-color | ||||
|    * .vjs-bg-opacity | ||||
|    * .window-color | ||||
|    * .vjs-window-opacity  | ||||
|    * .vjs-window-opacity | ||||
|    * | ||||
|    * @return {Object}  | ||||
|    * @return {Object} | ||||
|    * @method getValues | ||||
|    */ | ||||
|   getValues() { | ||||
|     const el = this.el(); | ||||
|  | ||||
|     const textEdge = getSelectedOptionValue(el.querySelector('.vjs-edge-style select')); | ||||
|     const fontFamily = getSelectedOptionValue(el.querySelector('.vjs-font-family select')); | ||||
|     const fgColor = getSelectedOptionValue(el.querySelector('.vjs-fg-color > select')); | ||||
|     const textOpacity = getSelectedOptionValue(el.querySelector('.vjs-text-opacity > select')); | ||||
|     const bgColor = getSelectedOptionValue(el.querySelector('.vjs-bg-color > select')); | ||||
|     const bgOpacity = getSelectedOptionValue(el.querySelector('.vjs-bg-opacity > select')); | ||||
|     const windowColor = getSelectedOptionValue(el.querySelector('.window-color > select')); | ||||
|     const windowOpacity = getSelectedOptionValue(el.querySelector('.vjs-window-opacity > select')); | ||||
|     const fontPercent = window['parseFloat'](getSelectedOptionValue(el.querySelector('.vjs-font-percent > select'))); | ||||
|     const textEdge = getSelectedOptionValue(this.$('.vjs-edge-style select')); | ||||
|     const fontFamily = getSelectedOptionValue(this.$('.vjs-font-family select')); | ||||
|     const fgColor = getSelectedOptionValue(this.$('.vjs-fg-color > select')); | ||||
|     const textOpacity = getSelectedOptionValue(this.$('.vjs-text-opacity > select')); | ||||
|     const bgColor = getSelectedOptionValue(this.$('.vjs-bg-color > select')); | ||||
|     const bgOpacity = getSelectedOptionValue(this.$('.vjs-bg-opacity > select')); | ||||
|     const windowColor = getSelectedOptionValue(this.$('.window-color > select')); | ||||
|     const windowOpacity = getSelectedOptionValue(this.$('.vjs-window-opacity > select')); | ||||
|     const fontPercent = window['parseFloat'](getSelectedOptionValue(this.$('.vjs-font-percent > select'))); | ||||
|  | ||||
|     let result = { | ||||
|       'backgroundOpacity': bgOpacity, | ||||
| @@ -121,7 +119,7 @@ class TextTrackSettings extends Component { | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Set texttrack settings  | ||||
|    * Set texttrack settings | ||||
|    * Settings are | ||||
|    * .vjs-edge-style | ||||
|    * .vjs-font-family | ||||
| @@ -130,22 +128,20 @@ class TextTrackSettings extends Component { | ||||
|    * .vjs-bg-color | ||||
|    * .vjs-bg-opacity | ||||
|    * .window-color | ||||
|    * .vjs-window-opacity  | ||||
|    * .vjs-window-opacity | ||||
|    * | ||||
|    * @param {Object} values Object with texttrack setting values | ||||
|    * @method setValues | ||||
|    */ | ||||
|   setValues(values) { | ||||
|     const el = this.el(); | ||||
|  | ||||
|     setSelectedOption(el.querySelector('.vjs-edge-style select'), values.edgeStyle); | ||||
|     setSelectedOption(el.querySelector('.vjs-font-family select'), values.fontFamily); | ||||
|     setSelectedOption(el.querySelector('.vjs-fg-color > select'), values.color); | ||||
|     setSelectedOption(el.querySelector('.vjs-text-opacity > select'), values.textOpacity); | ||||
|     setSelectedOption(el.querySelector('.vjs-bg-color > select'), values.backgroundColor); | ||||
|     setSelectedOption(el.querySelector('.vjs-bg-opacity > select'), values.backgroundOpacity); | ||||
|     setSelectedOption(el.querySelector('.window-color > select'), values.windowColor); | ||||
|     setSelectedOption(el.querySelector('.vjs-window-opacity > select'), values.windowOpacity); | ||||
|     setSelectedOption(this.$('.vjs-edge-style select'), values.edgeStyle); | ||||
|     setSelectedOption(this.$('.vjs-font-family select'), values.fontFamily); | ||||
|     setSelectedOption(this.$('.vjs-fg-color > select'), values.color); | ||||
|     setSelectedOption(this.$('.vjs-text-opacity > select'), values.textOpacity); | ||||
|     setSelectedOption(this.$('.vjs-bg-color > select'), values.backgroundColor); | ||||
|     setSelectedOption(this.$('.vjs-bg-opacity > select'), values.backgroundOpacity); | ||||
|     setSelectedOption(this.$('.window-color > select'), values.windowColor); | ||||
|     setSelectedOption(this.$('.vjs-window-opacity > select'), values.windowOpacity); | ||||
|  | ||||
|     let fontPercent = values.fontPercent; | ||||
|  | ||||
| @@ -153,11 +149,11 @@ class TextTrackSettings extends Component { | ||||
|       fontPercent = fontPercent.toFixed(2); | ||||
|     } | ||||
|  | ||||
|     setSelectedOption(el.querySelector('.vjs-font-percent > select'), fontPercent); | ||||
|     setSelectedOption(this.$('.vjs-font-percent > select'), fontPercent); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Restore texttrack settings  | ||||
|    * Restore texttrack settings | ||||
|    * | ||||
|    * @method restoreSettings | ||||
|    */ | ||||
| @@ -174,7 +170,7 @@ class TextTrackSettings extends Component { | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Save texttrack settings to local storage  | ||||
|    * Save texttrack settings to local storage | ||||
|    * | ||||
|    * @method saveSettings | ||||
|    */ | ||||
| @@ -194,7 +190,7 @@ class TextTrackSettings extends Component { | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Update display of texttrack settings   | ||||
|    * Update display of texttrack settings | ||||
|    * | ||||
|    * @method updateDisplay | ||||
|    */ | ||||
|   | ||||
| @@ -7,6 +7,59 @@ import  * as Guid from './guid.js'; | ||||
| import log from './log.js'; | ||||
| import tsml from 'tsml'; | ||||
|  | ||||
| /** | ||||
|  * Detect if a value is a string with any non-whitespace characters. | ||||
|  * | ||||
|  * @param  {String} str | ||||
|  * @return {Boolean} | ||||
|  */ | ||||
| function isNonBlankString(str) { | ||||
|   return typeof str === 'string' && /\S/.test(str); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Throws an error if the passed string has whitespace. This is used by | ||||
|  * class methods to be relatively consistent with the classList API. | ||||
|  * | ||||
|  * @param  {String} str | ||||
|  * @return {Boolean} | ||||
|  */ | ||||
| function throwIfWhitespace(str) { | ||||
|   if (/\s/.test(str)) { | ||||
|     throw new Error('class has illegal whitespace characters'); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Produce a regular expression for matching a class name. | ||||
|  * | ||||
|  * @param  {String} className | ||||
|  * @return {RegExp} | ||||
|  */ | ||||
| function classRegExp(className) { | ||||
|   return new RegExp('(^|\\s)' + className + '($|\\s)'); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Creates functions to query the DOM using a given method. | ||||
|  * | ||||
|  * @function createQuerier | ||||
|  * @private | ||||
|  * @param  {String} method | ||||
|  * @return {Function} | ||||
|  */ | ||||
| function createQuerier(method) { | ||||
|   return function (selector, context) { | ||||
|     if (!isNonBlankString(selector)) { | ||||
|       return document[method](null); | ||||
|     } | ||||
|     if (isNonBlankString(context)) { | ||||
|       context = document.querySelector(context); | ||||
|     } | ||||
|     return (isEl(context) ? context : document)[method](selector); | ||||
|   }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Shorthand for document.getElementById() | ||||
|  * Also allows for CSS (jQuery) ID syntax. But nothing other than IDs. | ||||
| @@ -181,47 +234,99 @@ export function removeElData(el) { | ||||
| /** | ||||
|  * Check if an element has a CSS class | ||||
|  * | ||||
|  * @function hasElClass | ||||
|  * @param {Element} element Element to check | ||||
|  * @param {String} classToCheck Classname to check | ||||
|  * @function hasElClass | ||||
|  */ | ||||
| export function hasElClass(element, classToCheck) { | ||||
|   return ((' ' + element.className + ' ').indexOf(' ' + classToCheck + ' ') !== -1); | ||||
|   if (element.classList) { | ||||
|     return element.classList.contains(classToCheck); | ||||
|   } else { | ||||
|     throwIfWhitespace(classToCheck); | ||||
|     return classRegExp(classToCheck).test(element.className); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Add a CSS class name to an element | ||||
|  * | ||||
|  * @function addElClass | ||||
|  * @param {Element} element    Element to add class name to | ||||
|  * @param {String} classToAdd Classname to add | ||||
|  * @function addElClass | ||||
|  */ | ||||
| export function addElClass(element, classToAdd) { | ||||
|   if (!hasElClass(element, classToAdd)) { | ||||
|     element.className = element.className === '' ? classToAdd : element.className + ' ' + classToAdd; | ||||
|   if (element.classList) { | ||||
|     element.classList.add(classToAdd); | ||||
|  | ||||
|   // Don't need to `throwIfWhitespace` here because `hasElClass` will do it | ||||
|   // in the case of classList not being supported. | ||||
|   } else if (!hasElClass(element, classToAdd)) { | ||||
|     element.className = (element.className + ' ' + classToAdd).trim(); | ||||
|   } | ||||
|  | ||||
|   return element; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Remove a CSS class name from an element | ||||
|  * | ||||
|  * @function removeElClass | ||||
|  * @param {Element} element    Element to remove from class name | ||||
|  * @param {String} classToRemove Classname to remove | ||||
|  * @function removeElClass | ||||
|  */ | ||||
| export function removeElClass(element, classToRemove) { | ||||
|   if (!hasElClass(element, classToRemove)) {return;} | ||||
|  | ||||
|   let classNames = element.className.split(' '); | ||||
|  | ||||
|   // no arr.indexOf in ie8, and we don't want to add a big shim | ||||
|   for (let i = classNames.length - 1; i >= 0; i--) { | ||||
|     if (classNames[i] === classToRemove) { | ||||
|       classNames.splice(i,1); | ||||
|     } | ||||
|   if (element.classList) { | ||||
|     element.classList.remove(classToRemove); | ||||
|   } else { | ||||
|     throwIfWhitespace(classToRemove); | ||||
|     element.className = element.className.split(/\s+/).filter(function(c) { | ||||
|       return c !== classToRemove; | ||||
|     }).join(' '); | ||||
|   } | ||||
|  | ||||
|   element.className = classNames.join(' '); | ||||
|   return element; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Adds or removes a CSS class name on an element depending on an optional | ||||
|  * condition or the presence/absence of the class name. | ||||
|  * | ||||
|  * @function toggleElClass | ||||
|  * @param    {Element} element | ||||
|  * @param    {String} classToToggle | ||||
|  * @param    {Boolean|Function} [predicate] | ||||
|  *           Can be a function that returns a Boolean. If `true`, the class | ||||
|  *           will be added; if `false`, the class will be removed. If not | ||||
|  *           given, the class will be added if not present and vice versa. | ||||
|  */ | ||||
| export function toggleElClass(element, classToToggle, predicate) { | ||||
|  | ||||
|   // This CANNOT use `classList` internally because IE does not support the | ||||
|   // second parameter to the `classList.toggle()` method! Which is fine because | ||||
|   // `classList` will be used by the add/remove functions. | ||||
|   let has = hasElClass(element, classToToggle); | ||||
|  | ||||
|   if (typeof predicate === 'function') { | ||||
|     predicate = predicate(element, classToToggle); | ||||
|   } | ||||
|  | ||||
|   if (typeof predicate !== 'boolean') { | ||||
|     predicate = !has; | ||||
|   } | ||||
|  | ||||
|   // If the necessary class operation matches the current state of the | ||||
|   // element, no action is required. | ||||
|   if (predicate === has) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (predicate) { | ||||
|     addElClass(element, classToToggle); | ||||
|   } else { | ||||
|     removeElClass(element, classToToggle); | ||||
|   } | ||||
|  | ||||
|   return element; | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -292,7 +397,7 @@ export function getElAttributes(tag) { | ||||
|  * Attempt to block the ability to select text while dragging controls | ||||
|  * | ||||
|  * @return {Boolean} | ||||
|  * @method blockTextSelection | ||||
|  * @function blockTextSelection | ||||
|  */ | ||||
| export function blockTextSelection() { | ||||
|   document.body.focus(); | ||||
| @@ -305,7 +410,7 @@ export function blockTextSelection() { | ||||
|  * Turn off text selection blocking | ||||
|  * | ||||
|  * @return {Boolean} | ||||
|  * @method unblockTextSelection | ||||
|  * @function unblockTextSelection | ||||
|  */ | ||||
| export function unblockTextSelection() { | ||||
|   document.onselectstart = function() { | ||||
| @@ -318,9 +423,9 @@ export function unblockTextSelection() { | ||||
|  * getBoundingClientRect technique from | ||||
|  * John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/ | ||||
|  * | ||||
|  * @function findElPosition | ||||
|  * @param {Element} el Element from which to get offset | ||||
|  * @return {Object=} | ||||
|  * @method findElPosition | ||||
|  * @return {Object} | ||||
|  */ | ||||
| export function findElPosition(el) { | ||||
|   let box; | ||||
| @@ -359,10 +464,10 @@ export function findElPosition(el) { | ||||
|  * Returns an object with x and y coordinates. | ||||
|  * The base on the coordinates are the bottom left of the element. | ||||
|  * | ||||
|  * @function getPointerPosition | ||||
|  * @param {Element} el Element on which to get the pointer position on | ||||
|  * @param {Event} event Event object | ||||
|  * @return {Object=} position This object will have x and y coordinates corresponding to the mouse position | ||||
|  * @metho getPointerPosition | ||||
|  * @return {Object} This object will have x and y coordinates corresponding to the mouse position | ||||
|  */ | ||||
| export function getPointerPosition(el, event) { | ||||
|   let position = {}; | ||||
| @@ -389,8 +494,9 @@ export function getPointerPosition(el, event) { | ||||
| /** | ||||
|  * Determines, via duck typing, whether or not a value is a DOM element. | ||||
|  * | ||||
|  * @param  {Mixed} value | ||||
|  * @return {Boolean} | ||||
|  * @function isEl | ||||
|  * @param    {Mixed} value | ||||
|  * @return   {Boolean} | ||||
|  */ | ||||
| export function isEl(value) { | ||||
|   return !!value && typeof value === 'object' && value.nodeType === 1; | ||||
| @@ -427,18 +533,25 @@ export function emptyEl(el) { | ||||
|  * from falling into the trap of simply writing to `innerHTML`, which is | ||||
|  * an XSS concern. | ||||
|  * | ||||
|  * The content for an element can be passed in multiple types, whose | ||||
|  * behavior is as follows: | ||||
|  * The content for an element can be passed in multiple types and | ||||
|  * combinations, whose behavior is as follows: | ||||
|  * | ||||
|  * - String: Normalized into a text node. | ||||
|  * - Node: An Element or TextNode is passed through. | ||||
|  * - Array: A one-dimensional array of strings, nodes, or functions (which | ||||
|  *   return single strings or nodes). | ||||
|  * - Function: If the sole argument, is expected to produce a string, node, | ||||
|  *   or array. | ||||
|  * - String | ||||
|  *   Normalized into a text node. | ||||
|  * | ||||
|  * - Element, TextNode | ||||
|  *   Passed through. | ||||
|  * | ||||
|  * - Array | ||||
|  *   A one-dimensional array of strings, elements, nodes, or functions (which | ||||
|  *   return single strings, elements, or nodes). | ||||
|  * | ||||
|  * - Function | ||||
|  *   If the sole argument, is expected to produce a string, element, | ||||
|  *   node, or array. | ||||
|  * | ||||
|  * @function normalizeContent | ||||
|  * @param    {String|Element|Array|Function} content | ||||
|  * @param    {String|Element|TextNode|Array|Function} content | ||||
|  * @return   {Array} | ||||
|  */ | ||||
| export function normalizeContent(content) { | ||||
| @@ -474,7 +587,8 @@ export function normalizeContent(content) { | ||||
|  * | ||||
|  * @function appendContent | ||||
|  * @param    {Element} el | ||||
|  * @param    {String|Element|Array|Function} content | ||||
|  * @param    {String|Element|TextNode|Array|Function} content | ||||
|  *           See: `normalizeContent` | ||||
|  * @return   {Element} | ||||
|  */ | ||||
| export function appendContent(el, content) { | ||||
| @@ -488,9 +602,46 @@ export function appendContent(el, content) { | ||||
|  * | ||||
|  * @function insertContent | ||||
|  * @param    {Element} el | ||||
|  * @param    {String|Element|Array|Function} content | ||||
|  * @param    {String|Element|TextNode|Array|Function} content | ||||
|  *           See: `normalizeContent` | ||||
|  * @return   {Element} | ||||
|  */ | ||||
| export function insertContent(el, content) { | ||||
|   return appendContent(emptyEl(el), content); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Finds a single DOM element matching `selector` within the optional | ||||
|  * `context` of another DOM element (defaulting to `document`). | ||||
|  * | ||||
|  * @function $ | ||||
|  * @param    {String} selector | ||||
|  *           A valid CSS selector, which will be passed to `querySelector`. | ||||
|  * | ||||
|  * @param    {Element|String} [context=document] | ||||
|  *           A DOM element within which to query. Can also be a selector | ||||
|  *           string in which case the first matching element will be used | ||||
|  *           as context. If missing (or no element matches selector), falls | ||||
|  *           back to `document`. | ||||
|  * | ||||
|  * @return   {Element|null} | ||||
|  */ | ||||
| export const $ = createQuerier('querySelector'); | ||||
|  | ||||
| /** | ||||
|  * Finds a all DOM elements matching `selector` within the optional | ||||
|  * `context` of another DOM element (defaulting to `document`). | ||||
|  * | ||||
|  * @function $$ | ||||
|  * @param    {String} selector | ||||
|  *           A valid CSS selector, which will be passed to `querySelectorAll`. | ||||
|  * | ||||
|  * @param    {Element|String} [context=document] | ||||
|  *           A DOM element within which to query. Can also be a selector | ||||
|  *           string in which case the first matching element will be used | ||||
|  *           as context. If missing (or no element matches selector), falls | ||||
|  *           back to `document`. | ||||
|  * | ||||
|  * @return   {NodeList} | ||||
|  */ | ||||
| export const $$ = createQuerier('querySelectorAll'); | ||||
|   | ||||
							
								
								
									
										163
									
								
								src/js/video.js
									
									
									
									
									
								
							
							
						
						
									
										163
									
								
								src/js/video.js
									
									
									
									
									
								
							| @@ -99,10 +99,10 @@ var videojs = function(id, options, ready){ | ||||
| }; | ||||
|  | ||||
| // Add default styles | ||||
| let style = document.querySelector('.vjs-styles-defaults'); | ||||
| let style = Dom.$('.vjs-styles-defaults'); | ||||
| if (!style) { | ||||
|   style = stylesheet.createStyleElement('vjs-styles-defaults'); | ||||
|   let head = document.querySelector('head'); | ||||
|   let head = Dom.$('head'); | ||||
|   head.insertBefore(style, head.firstChild); | ||||
|   stylesheet.setTextContent(style, ` | ||||
|     .video-js { | ||||
| @@ -545,22 +545,149 @@ videojs.xhr = xhr; | ||||
|  */ | ||||
| videojs.TextTrack = TextTrack; | ||||
|  | ||||
| // REMOVING: We probably should add this to the migration plugin | ||||
| // // Expose but deprecate the window[componentName] method for accessing components | ||||
| // Object.getOwnPropertyNames(Component.components).forEach(function(name){ | ||||
| //   let component = Component.components[name]; | ||||
| // | ||||
| //   // A deprecation warning as the constuctor | ||||
| //   module.exports[name] = function(player, options, ready){ | ||||
| //     log.warn('Using videojs.'+name+' to access the '+name+' component has been deprecated. Please use videojs.getComponent("componentName")'); | ||||
| // | ||||
| //     return new Component(player, options, ready); | ||||
| //   }; | ||||
| // | ||||
| //   // Allow the prototype and class methods to be accessible still this way | ||||
| //   // Though anything that attempts to override class methods will no longer work | ||||
| //   assign(module.exports[name], component); | ||||
| // }); | ||||
| /** | ||||
|  * Determines, via duck typing, whether or not a value is a DOM element. | ||||
|  * | ||||
|  * @method isEl | ||||
|  * @param  {Mixed} value | ||||
|  * @return {Boolean} | ||||
|  */ | ||||
| videojs.isEl = Dom.isEl; | ||||
|  | ||||
| /** | ||||
|  * Determines, via duck typing, whether or not a value is a text node. | ||||
|  * | ||||
|  * @method isTextNode | ||||
|  * @param  {Mixed} value | ||||
|  * @return {Boolean} | ||||
|  */ | ||||
| videojs.isTextNode = Dom.isTextNode; | ||||
|  | ||||
| /** | ||||
|  * Check if an element has a CSS class | ||||
|  * | ||||
|  * @method hasClass | ||||
|  * @param {Element} element Element to check | ||||
|  * @param {String} classToCheck Classname to check | ||||
|  */ | ||||
| videojs.hasClass = Dom.hasElClass; | ||||
|  | ||||
| /** | ||||
|  * Add a CSS class name to an element | ||||
|  * | ||||
|  * @method addClass | ||||
|  * @param {Element} element    Element to add class name to | ||||
|  * @param {String} classToAdd Classname to add | ||||
|  */ | ||||
| videojs.addClass = Dom.addElClass; | ||||
|  | ||||
| /** | ||||
|  * Remove a CSS class name from an element | ||||
|  * | ||||
|  * @method removeClass | ||||
|  * @param {Element} element    Element to remove from class name | ||||
|  * @param {String} classToRemove Classname to remove | ||||
|  */ | ||||
| videojs.removeClass = Dom.removeElClass; | ||||
|  | ||||
| /** | ||||
|  * Adds or removes a CSS class name on an element depending on an optional | ||||
|  * condition or the presence/absence of the class name. | ||||
|  * | ||||
|  * @method toggleElClass | ||||
|  * @param  {Element} element | ||||
|  * @param  {String} classToToggle | ||||
|  * @param  {Boolean|Function} [predicate] | ||||
|  *         Can be a function that returns a Boolean. If `true`, the class | ||||
|  *         will be added; if `false`, the class will be removed. If not | ||||
|  *         given, the class will be added if not present and vice versa. | ||||
|  */ | ||||
| videojs.toggleClass = Dom.toggleElClass; | ||||
|  | ||||
| /** | ||||
|  * Apply attributes to an HTML element. | ||||
|  * | ||||
|  * @method setAttributes | ||||
|  * @param  {Element} el         Target element. | ||||
|  * @param  {Object=} attributes Element attributes to be applied. | ||||
|  */ | ||||
| videojs.setAttributes = Dom.setElAttributes; | ||||
|  | ||||
| /** | ||||
|  * Get an element's attribute values, as defined on the HTML tag | ||||
|  * Attributes are not the same as properties. They're defined on the tag | ||||
|  * or with setAttribute (which shouldn't be used with HTML) | ||||
|  * This will return true or false for boolean attributes. | ||||
|  * | ||||
|  * @method getAttributes | ||||
|  * @param  {Element} tag Element from which to get tag attributes | ||||
|  * @return {Object} | ||||
|  */ | ||||
| videojs.getAttributes = Dom.getElAttributes; | ||||
|  | ||||
| /** | ||||
|  * Empties the contents of an element. | ||||
|  * | ||||
|  * @method emptyEl | ||||
|  * @param  {Element} el | ||||
|  * @return {Element} | ||||
|  */ | ||||
| videojs.emptyEl = Dom.emptyEl; | ||||
|  | ||||
| /** | ||||
|  * Normalizes and appends content to an element. | ||||
|  * | ||||
|  * The content for an element can be passed in multiple types and | ||||
|  * combinations, whose behavior is as follows: | ||||
|  * | ||||
|  * - String | ||||
|  *   Normalized into a text node. | ||||
|  * | ||||
|  * - Element, TextNode | ||||
|  *   Passed through. | ||||
|  * | ||||
|  * - Array | ||||
|  *   A one-dimensional array of strings, elements, nodes, or functions (which | ||||
|  *   return single strings, elements, or nodes). | ||||
|  * | ||||
|  * - Function | ||||
|  *   If the sole argument, is expected to produce a string, element, | ||||
|  *   node, or array. | ||||
|  * | ||||
|  * @method appendContent | ||||
|  * @param  {Element} el | ||||
|  * @param  {String|Element|TextNode|Array|Function} content | ||||
|  * @return {Element} | ||||
|  */ | ||||
| videojs.appendContent = Dom.appendContent; | ||||
|  | ||||
| /** | ||||
|  * Normalizes and inserts content into an element; this is identical to | ||||
|  * `appendContent()`, except it empties the element first. | ||||
|  * | ||||
|  * The content for an element can be passed in multiple types and | ||||
|  * combinations, whose behavior is as follows: | ||||
|  * | ||||
|  * - String | ||||
|  *   Normalized into a text node. | ||||
|  * | ||||
|  * - Element, TextNode | ||||
|  *   Passed through. | ||||
|  * | ||||
|  * - Array | ||||
|  *   A one-dimensional array of strings, elements, nodes, or functions (which | ||||
|  *   return single strings, elements, or nodes). | ||||
|  * | ||||
|  * - Function | ||||
|  *   If the sole argument, is expected to produce a string, element, | ||||
|  *   node, or array. | ||||
|  * | ||||
|  * @method insertContent | ||||
|  * @param  {Element} el | ||||
|  * @param  {String|Element|TextNode|Array|Function} content | ||||
|  * @return {Element} | ||||
|  */ | ||||
| videojs.insertContent = Dom.insertContent; | ||||
|  | ||||
| /* | ||||
|  * Custom Universal Module Definition (UMD) | ||||
|   | ||||
| @@ -471,6 +471,10 @@ test('should add and remove a CSS class', function(){ | ||||
|   ok(comp.el().className.indexOf('test-class') !== -1); | ||||
|   comp.removeClass('test-class'); | ||||
|   ok(comp.el().className.indexOf('test-class') === -1); | ||||
|   comp.toggleClass('test-class'); | ||||
|   ok(comp.el().className.indexOf('test-class') !== -1); | ||||
|   comp.toggleClass('test-class'); | ||||
|   ok(comp.el().className.indexOf('test-class') === -1); | ||||
| }); | ||||
|  | ||||
| test('should show and hide an element', function(){ | ||||
| @@ -695,3 +699,18 @@ test('should provide interval methods that automatically get cleared on componen | ||||
|  | ||||
|   ok(intervalsFired === 5, 'Interval was cleared when component was disposed'); | ||||
| }); | ||||
|  | ||||
| test('$ and $$ functions', function() { | ||||
|   var comp = new Component(getFakePlayer()); | ||||
|   var contentEl = document.createElement('div'); | ||||
|   var children = [ | ||||
|     document.createElement('div'), | ||||
|     document.createElement('div') | ||||
|   ]; | ||||
|  | ||||
|   comp.contentEl_ = contentEl; | ||||
|   children.forEach(child => contentEl.appendChild(child)); | ||||
|  | ||||
|   strictEqual(comp.$('div'), children[0], '$ defaults to contentEl as scope'); | ||||
|   strictEqual(comp.$$('div').length, children.length, '$$ defaults to contentEl as scope'); | ||||
| }); | ||||
|   | ||||
| @@ -35,17 +35,17 @@ test('should update settings', function() { | ||||
|   player.textTrackSettings.setValues(newSettings); | ||||
|   deepEqual(player.textTrackSettings.getValues(), newSettings, 'values are updated'); | ||||
|  | ||||
|   equal(player.el().querySelector('.vjs-fg-color > select').selectedIndex, 1, 'fg-color is set to new value'); | ||||
|   equal(player.el().querySelector('.vjs-bg-color > select').selectedIndex, 1, 'bg-color is set to new value'); | ||||
|   equal(player.el().querySelector('.window-color > select').selectedIndex, 1, 'window-color is set to new value'); | ||||
|   equal(player.el().querySelector('.vjs-text-opacity > select').selectedIndex, 1, 'text-opacity is set to new value'); | ||||
|   equal(player.el().querySelector('.vjs-bg-opacity > select').selectedIndex, 1, 'bg-opacity is set to new value'); | ||||
|   equal(player.el().querySelector('.vjs-window-opacity > select').selectedIndex, 1, 'window-opacity is set to new value'); | ||||
|   equal(player.el().querySelector('.vjs-edge-style select').selectedIndex, 1, 'edge-style is set to new value'); | ||||
|   equal(player.el().querySelector('.vjs-font-family select').selectedIndex, 1, 'font-family is set to new value'); | ||||
|   equal(player.el().querySelector('.vjs-font-percent select').selectedIndex, 3, 'font-percent is set to new value'); | ||||
|   equal(player.$('.vjs-fg-color > select').selectedIndex, 1, 'fg-color is set to new value'); | ||||
|   equal(player.$('.vjs-bg-color > select').selectedIndex, 1, 'bg-color is set to new value'); | ||||
|   equal(player.$('.window-color > select').selectedIndex, 1, 'window-color is set to new value'); | ||||
|   equal(player.$('.vjs-text-opacity > select').selectedIndex, 1, 'text-opacity is set to new value'); | ||||
|   equal(player.$('.vjs-bg-opacity > select').selectedIndex, 1, 'bg-opacity is set to new value'); | ||||
|   equal(player.$('.vjs-window-opacity > select').selectedIndex, 1, 'window-opacity is set to new value'); | ||||
|   equal(player.$('.vjs-edge-style select').selectedIndex, 1, 'edge-style is set to new value'); | ||||
|   equal(player.$('.vjs-font-family select').selectedIndex, 1, 'font-family is set to new value'); | ||||
|   equal(player.$('.vjs-font-percent select').selectedIndex, 3, 'font-percent is set to new value'); | ||||
|  | ||||
|   Events.trigger(player.el().querySelector('.vjs-done-button'), 'click'); | ||||
|   Events.trigger(player.$('.vjs-done-button'), 'click'); | ||||
|   deepEqual(safeParseTuple(window.localStorage.getItem('vjs-text-track-settings'))[1], newSettings, 'values are saved'); | ||||
|  | ||||
|   player.dispose(); | ||||
| @@ -57,32 +57,32 @@ test('should restore default settings', function() { | ||||
|     persistTextTrackSettings: true | ||||
|   }); | ||||
|  | ||||
|   player.el().querySelector('.vjs-fg-color > select').selectedIndex = 1; | ||||
|   player.el().querySelector('.vjs-bg-color > select').selectedIndex = 1; | ||||
|   player.el().querySelector('.window-color > select').selectedIndex = 1; | ||||
|   player.el().querySelector('.vjs-text-opacity > select').selectedIndex = 1; | ||||
|   player.el().querySelector('.vjs-bg-opacity > select').selectedIndex = 1; | ||||
|   player.el().querySelector('.vjs-window-opacity > select').selectedIndex = 1; | ||||
|   player.el().querySelector('.vjs-edge-style select').selectedIndex = 1; | ||||
|   player.el().querySelector('.vjs-font-family select').selectedIndex = 1; | ||||
|   player.el().querySelector('.vjs-font-percent select').selectedIndex = 3; | ||||
|   player.$('.vjs-fg-color > select').selectedIndex = 1; | ||||
|   player.$('.vjs-bg-color > select').selectedIndex = 1; | ||||
|   player.$('.window-color > select').selectedIndex = 1; | ||||
|   player.$('.vjs-text-opacity > select').selectedIndex = 1; | ||||
|   player.$('.vjs-bg-opacity > select').selectedIndex = 1; | ||||
|   player.$('.vjs-window-opacity > select').selectedIndex = 1; | ||||
|   player.$('.vjs-edge-style select').selectedIndex = 1; | ||||
|   player.$('.vjs-font-family select').selectedIndex = 1; | ||||
|   player.$('.vjs-font-percent select').selectedIndex = 3; | ||||
|  | ||||
|   Events.trigger(player.el().querySelector('.vjs-done-button'), 'click'); | ||||
|   Events.trigger(player.el().querySelector('.vjs-default-button'), 'click'); | ||||
|   Events.trigger(player.el().querySelector('.vjs-done-button'), 'click'); | ||||
|   Events.trigger(player.$('.vjs-done-button'), 'click'); | ||||
|   Events.trigger(player.$('.vjs-default-button'), 'click'); | ||||
|   Events.trigger(player.$('.vjs-done-button'), 'click'); | ||||
|  | ||||
|   deepEqual(player.textTrackSettings.getValues(), {}, 'values are defaulted'); | ||||
|   deepEqual(window.localStorage.getItem('vjs-text-track-settings'), null, 'values are saved'); | ||||
|  | ||||
|   equal(player.el().querySelector('.vjs-fg-color > select').selectedIndex, 0, 'fg-color is set to default value'); | ||||
|   equal(player.el().querySelector('.vjs-bg-color > select').selectedIndex, 0, 'bg-color is set to default value'); | ||||
|   equal(player.el().querySelector('.window-color > select').selectedIndex, 0, 'window-color is set to default value'); | ||||
|   equal(player.el().querySelector('.vjs-text-opacity > select').selectedIndex, 0, 'text-opacity is set to default value'); | ||||
|   equal(player.el().querySelector('.vjs-bg-opacity > select').selectedIndex, 0, 'bg-opacity is set to default value'); | ||||
|   equal(player.el().querySelector('.vjs-window-opacity > select').selectedIndex, 0, 'window-opacity is set to default value'); | ||||
|   equal(player.el().querySelector('.vjs-edge-style select').selectedIndex, 0, 'edge-style is set to default value'); | ||||
|   equal(player.el().querySelector('.vjs-font-family select').selectedIndex, 0, 'font-family is set to default value'); | ||||
|   equal(player.el().querySelector('.vjs-font-percent select').selectedIndex, 2, 'font-percent is set to default value'); | ||||
|   equal(player.$('.vjs-fg-color > select').selectedIndex, 0, 'fg-color is set to default value'); | ||||
|   equal(player.$('.vjs-bg-color > select').selectedIndex, 0, 'bg-color is set to default value'); | ||||
|   equal(player.$('.window-color > select').selectedIndex, 0, 'window-color is set to default value'); | ||||
|   equal(player.$('.vjs-text-opacity > select').selectedIndex, 0, 'text-opacity is set to default value'); | ||||
|   equal(player.$('.vjs-bg-opacity > select').selectedIndex, 0, 'bg-opacity is set to default value'); | ||||
|   equal(player.$('.vjs-window-opacity > select').selectedIndex, 0, 'window-opacity is set to default value'); | ||||
|   equal(player.$('.vjs-edge-style select').selectedIndex, 0, 'edge-style is set to default value'); | ||||
|   equal(player.$('.vjs-font-family select').selectedIndex, 0, 'font-family is set to default value'); | ||||
|   equal(player.$('.vjs-font-percent select').selectedIndex, 2, 'font-percent is set to default value'); | ||||
|  | ||||
|   player.dispose(); | ||||
| }); | ||||
| @@ -91,7 +91,7 @@ test('should open on click', function() { | ||||
|   var player = TestHelpers.makePlayer({ | ||||
|     tracks: tracks | ||||
|   }); | ||||
|   Events.trigger(player.el().querySelector('.vjs-texttrack-settings'), 'click'); | ||||
|   Events.trigger(player.$('.vjs-texttrack-settings'), 'click'); | ||||
|   ok(!player.textTrackSettings.hasClass('vjs-hidden'), 'settings open'); | ||||
|  | ||||
|   player.dispose(); | ||||
| @@ -101,8 +101,8 @@ test('should close on done click', function() { | ||||
|   var player = TestHelpers.makePlayer({ | ||||
|     tracks: tracks | ||||
|   }); | ||||
|   Events.trigger(player.el().querySelector('.vjs-texttrack-settings'), 'click'); | ||||
|   Events.trigger(player.el().querySelector('.vjs-done-button'), 'click'); | ||||
|   Events.trigger(player.$('.vjs-texttrack-settings'), 'click'); | ||||
|   Events.trigger(player.$('.vjs-done-button'), 'click'); | ||||
|   ok(player.textTrackSettings.hasClass('vjs-hidden'), 'settings closed'); | ||||
|  | ||||
|   player.dispose(); | ||||
| @@ -141,7 +141,7 @@ test('if persist option is set, save settings when "done"', function() { | ||||
|     save++; | ||||
|   }; | ||||
|  | ||||
|   Events.trigger(player.el().querySelector('.vjs-done-button'), 'click'); | ||||
|   Events.trigger(player.$('.vjs-done-button'), 'click'); | ||||
|  | ||||
|   equal(save, 1, 'save was called'); | ||||
|  | ||||
| @@ -171,7 +171,7 @@ test('do not try to restore or save settings if persist option is not set', func | ||||
|  | ||||
|   equal(restore, 0, 'restore was not called'); | ||||
|  | ||||
|   Events.trigger(player.el().querySelector('.vjs-done-button'), 'click'); | ||||
|   Events.trigger(player.$('.vjs-done-button'), 'click'); | ||||
|  | ||||
|   // saveSettings is called but does nothing | ||||
|   equal(save, 1, 'save was not called'); | ||||
|   | ||||
| @@ -62,23 +62,164 @@ test('should get and remove data from an element', function(){ | ||||
|   ok(!Dom.hasElData(el), 'cached item emptied'); | ||||
| }); | ||||
|  | ||||
| test('should add and remove a class name on an element', function(){ | ||||
| test('addElClass()', function(){ | ||||
|   var el = document.createElement('div'); | ||||
|  | ||||
|   expect(5); | ||||
|  | ||||
|   Dom.addElClass(el, 'test-class'); | ||||
|   ok(el.className === 'test-class', 'class added'); | ||||
|   strictEqual(el.className, 'test-class', 'adds a single class'); | ||||
|  | ||||
|   Dom.addElClass(el, 'test-class'); | ||||
|   ok(el.className === 'test-class', 'same class not duplicated'); | ||||
|   Dom.addElClass(el, 'test-class2'); | ||||
|   ok(el.className === 'test-class test-class2', 'added second class'); | ||||
|   Dom.removeElClass(el, 'test-class'); | ||||
|   ok(el.className === 'test-class2', 'removed first class'); | ||||
|   strictEqual(el.className, 'test-class', 'does not duplicate classes'); | ||||
|  | ||||
|   throws(function(){ | ||||
|     Dom.addElClass(el, 'foo foo-bar'); | ||||
|   }, 'throws when attempting to add a class with whitespace'); | ||||
|  | ||||
|   Dom.addElClass(el, 'test2_className'); | ||||
|   strictEqual(el.className, 'test-class test2_className', 'adds second class'); | ||||
|  | ||||
|   Dom.addElClass(el, 'FOO'); | ||||
|   strictEqual(el.className, 'test-class test2_className FOO', 'adds third class'); | ||||
| }); | ||||
|  | ||||
| test('should read class names on an element', function(){ | ||||
| test('removeElClass()', function() { | ||||
|   var el = document.createElement('div'); | ||||
|   Dom.addElClass(el, 'test-class1'); | ||||
|   ok(Dom.hasElClass(el, 'test-class1') === true, 'class detected'); | ||||
|   ok(Dom.hasElClass(el, 'test-class') === false, 'substring correctly not detected'); | ||||
|  | ||||
|   el.className = 'test-class foo foo test2_className FOO bar'; | ||||
|  | ||||
|   expect(5); | ||||
|  | ||||
|   Dom.removeElClass(el, 'test-class'); | ||||
|   strictEqual(el.className, 'foo foo test2_className FOO bar', 'removes one class'); | ||||
|  | ||||
|   Dom.removeElClass(el, 'foo'); | ||||
|   strictEqual(el.className, 'test2_className FOO bar', 'removes all instances of a class'); | ||||
|  | ||||
|   throws(function(){ | ||||
|     Dom.removeElClass(el, 'test2_className bar'); | ||||
|   }, 'throws when attempting to remove a class with whitespace'); | ||||
|  | ||||
|   Dom.removeElClass(el, 'test2_className'); | ||||
|   strictEqual(el.className, 'FOO bar', 'removes another class'); | ||||
|  | ||||
|   Dom.removeElClass(el, 'FOO'); | ||||
|   strictEqual(el.className, 'bar', 'removes another class'); | ||||
| }); | ||||
|  | ||||
| test('hasElClass()', function(){ | ||||
|   var el = document.createElement('div'); | ||||
|  | ||||
|   el.className = 'test-class foo foo test2_className FOO bar'; | ||||
|  | ||||
|   strictEqual(Dom.hasElClass(el, 'test-class'), true, 'class detected'); | ||||
|   strictEqual(Dom.hasElClass(el, 'foo'), true, 'class detected'); | ||||
|   strictEqual(Dom.hasElClass(el, 'test2_className'), true, 'class detected'); | ||||
|   strictEqual(Dom.hasElClass(el, 'FOO'), true, 'class detected'); | ||||
|   strictEqual(Dom.hasElClass(el, 'bar'), true, 'class detected'); | ||||
|   strictEqual(Dom.hasElClass(el, 'test2'), false, 'valid substring - but not a class - correctly not detected'); | ||||
|   strictEqual(Dom.hasElClass(el, 'className'), false, 'valid substring - but not a class - correctly not detected'); | ||||
|  | ||||
|   throws(function(){ | ||||
|     Dom.hasElClass(el, 'FOO bar'); | ||||
|   }, 'throws when attempting to detect a class with whitespace'); | ||||
| }); | ||||
|  | ||||
| test('toggleElClass()', function() { | ||||
|   let el = Dom.createEl('div', {className: 'foo bar'}); | ||||
|  | ||||
|   let predicateToggles = [ | ||||
|     { | ||||
|       toggle: 'foo', | ||||
|       predicate: true, | ||||
|       className: 'foo bar', | ||||
|       message: 'if predicate `true` matches state of the element, do nothing' | ||||
|     }, | ||||
|     { | ||||
|       toggle: 'baz', | ||||
|       predicate: false, | ||||
|       className: 'foo bar', | ||||
|       message: 'if predicate `false` matches state of the element, do nothing' | ||||
|     }, | ||||
|     { | ||||
|       toggle: 'baz', | ||||
|       predicate: true, | ||||
|       className: 'foo bar baz', | ||||
|       message: 'if predicate `true` differs from state of the element, add the class' | ||||
|     }, | ||||
|     { | ||||
|       toggle: 'foo', | ||||
|       predicate: false, | ||||
|       className: 'bar baz', | ||||
|       message: 'if predicate `false` differs from state of the element, remove the class' | ||||
|     }, | ||||
|     { | ||||
|       toggle: 'bar', | ||||
|       predicate: () => true, | ||||
|       className: 'bar baz', | ||||
|       message: 'if a predicate function returns `true`, matching the state of the element, do nothing' | ||||
|     }, | ||||
|     { | ||||
|       toggle: 'foo', | ||||
|       predicate: () => false, | ||||
|       className: 'bar baz', | ||||
|       message: 'if a predicate function returns `false`, matching the state of the element, do nothing' | ||||
|     }, | ||||
|     { | ||||
|       toggle: 'foo', | ||||
|       predicate: () => true, | ||||
|       className: 'bar baz foo', | ||||
|       message: 'if a predicate function returns `true`, differing from state of the element, add the class' | ||||
|     }, | ||||
|     { | ||||
|       toggle: 'foo', | ||||
|       predicate: () => false, | ||||
|       className: 'bar baz', | ||||
|       message: 'if a predicate function returns `false`, differing from state of the element, remove the class' | ||||
|     }, | ||||
|     { | ||||
|       toggle: 'foo', | ||||
|       predicate: Function.prototype, | ||||
|       className: 'bar baz foo', | ||||
|       message: 'if a predicate function returns `undefined` and the element does not have the class, add the class' | ||||
|     }, | ||||
|     { | ||||
|       toggle: 'bar', | ||||
|       predicate: Function.prototype, | ||||
|       className: 'baz foo', | ||||
|       message: 'if a predicate function returns `undefined` and the element has the class, remove the class' | ||||
|     }, | ||||
|     { | ||||
|       toggle: 'bar', | ||||
|       predicate: () => [], | ||||
|       className: 'baz foo bar', | ||||
|       message: 'if a predicate function returns a defined non-boolean value and the element does not have the class, add the class' | ||||
|     }, | ||||
|     { | ||||
|       toggle: 'baz', | ||||
|       predicate: () => 'this is incorrect', | ||||
|       className: 'foo bar', | ||||
|       message: 'if a predicate function returns a defined non-boolean value and the element has the class, remove the class' | ||||
|     }, | ||||
|   ]; | ||||
|  | ||||
|   expect(3 + predicateToggles.length); | ||||
|  | ||||
|   Dom.toggleElClass(el, 'bar'); | ||||
|   strictEqual(el.className, 'foo', 'toggles a class off, if present'); | ||||
|  | ||||
|   Dom.toggleElClass(el, 'bar'); | ||||
|   strictEqual(el.className, 'foo bar', 'toggles a class on, if absent'); | ||||
|  | ||||
|   throws(function(){ | ||||
|     Dom.toggleElClass(el, 'foo bar'); | ||||
|   }, 'throws when attempting to toggle a class with whitespace'); | ||||
|  | ||||
|   predicateToggles.forEach(x => { | ||||
|     Dom.toggleElClass(el, x.toggle, x.predicate); | ||||
|     strictEqual(el.className, x.className, x.message); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| test('should set element attributes from object', function(){ | ||||
| @@ -293,4 +434,37 @@ test('Dom.appendContent', function(assert) { | ||||
|   assert.strictEqual(el.firstChild.nextSibling, p2, 'the second paragraph was appended'); | ||||
| }); | ||||
|  | ||||
| test('$() and $$()', function() { | ||||
|   let fixture = document.getElementById('qunit-fixture'); | ||||
|   let container = document.createElement('div'); | ||||
|   let children = [ | ||||
|     document.createElement('div'), | ||||
|     document.createElement('div'), | ||||
|     document.createElement('div'), | ||||
|   ]; | ||||
|  | ||||
|   children.forEach(child => container.appendChild(child)); | ||||
|   fixture.appendChild(container); | ||||
|  | ||||
|   let totalDivCount = document.getElementsByTagName('div').length; | ||||
|  | ||||
|   expect(12); | ||||
|  | ||||
|   strictEqual(Dom.$('#qunit-fixture'), fixture, 'can find an element in the document context'); | ||||
|   strictEqual(Dom.$$('div').length, totalDivCount, 'finds elements in the document context'); | ||||
|  | ||||
|   strictEqual(Dom.$('div', container), children[0], 'can find an element in a DOM element context'); | ||||
|   strictEqual(Dom.$$('div', container).length, children.length, 'finds elements in a DOM element context'); | ||||
|  | ||||
|   strictEqual(Dom.$('#qunit-fixture', document.querySelector('unknown')), fixture, 'falls back to document given a bad context element'); | ||||
|   strictEqual(Dom.$$('div', document.querySelector('unknown')).length, totalDivCount, 'falls back to document given a bad context element'); | ||||
|  | ||||
|   strictEqual(Dom.$('#qunit-fixture', 'body'), fixture, 'can find an element in a selector context'); | ||||
|   strictEqual(Dom.$$('div', '#qunit-fixture').length, 1 + children.length, 'finds elements in a selector context'); | ||||
|  | ||||
|   strictEqual(Dom.$('#qunit-fixture', 'unknown'), fixture, 'falls back to document given a bad context selector'); | ||||
|   strictEqual(Dom.$$('div', 'unknown').length, totalDivCount, 'falls back to document given a bad context selector'); | ||||
|  | ||||
|   strictEqual(Dom.$('div', children[0]), null, 'returns null for missing elements'); | ||||
|   strictEqual(Dom.$$('div', children[0]).length, 0, 'returns 0 for missing elements'); | ||||
| }); | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import videojs from '../../src/js/video.js'; | ||||
| import TestHelpers from './test-helpers.js'; | ||||
| import Player from '../../src/js/player.js'; | ||||
| import * as Dom from '../../src/js/utils/dom.js'; | ||||
| import log from '../../src/js/utils/log.js'; | ||||
| import document from 'global/document'; | ||||
|  | ||||
| @@ -78,3 +79,29 @@ test('should expose options and players properties for backward-compatibility', | ||||
|   ok(typeof videojs.options, 'object', 'options should be an object'); | ||||
|   ok(typeof videojs.players, 'object', 'players should be an object'); | ||||
| }); | ||||
|  | ||||
| test('should expose DOM functions', function() { | ||||
|  | ||||
|   // Keys are videojs methods, values are Dom methods. | ||||
|   let methods = { | ||||
|     isEl: 'isEl', | ||||
|     isTextNode: 'isTextNode', | ||||
|     hasClass: 'hasElClass', | ||||
|     addClass: 'addElClass', | ||||
|     removeClass: 'removeElClass', | ||||
|     toggleClass: 'toggleElClass', | ||||
|     setAttributes: 'setElAttributes', | ||||
|     getAttributes: 'getElAttributes', | ||||
|     emptyEl: 'emptyEl', | ||||
|     insertContent: 'insertContent', | ||||
|     appendContent: 'appendContent' | ||||
|   }; | ||||
|  | ||||
|   let keys = Object.keys(methods); | ||||
|  | ||||
|   expect(keys.length); | ||||
|   keys.forEach(function(vjsName) { | ||||
|     let domName = methods[vjsName]; | ||||
|     strictEqual(videojs[vjsName], Dom[domName], `videojs.${vjsName} is a reference to Dom.${domName}`); | ||||
|   }); | ||||
| }); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user