1
0
mirror of https://github.com/videojs/video.js.git synced 2025-01-10 23:30:03 +02:00

@misteroneill exposed DOM helpers. closes #2754

This commit is contained in:
Pat O'Neill 2015-11-09 17:43:17 -05:00 committed by Gary Katsevman
parent cd800b02ea
commit f2fa8f8687
13 changed files with 709 additions and 154 deletions

View File

@ -41,6 +41,7 @@
"stop",
"strictEqual",
"test",
"throws",
"sinon"
]
}

View File

@ -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))
--------------------

View File

@ -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
*

View File

@ -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

View File

@ -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--) {

View File

@ -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);

View File

@ -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
*/

View File

@ -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');

View File

@ -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)

View File

@ -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');
});

View File

@ -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');

View File

@ -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');
});

View File

@ -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}`);
});
});