1
0
mirror of https://github.com/videojs/video.js.git synced 2025-02-08 12:05:47 +02:00

refactor(texttracksettings): DRYer code and remove massive HTML blob (#3679)

* DRYer code while keeping tests passing
* Replace massive HTML blob with DOM methods
* Create obj util and implement it
This commit is contained in:
Pat O'Neill 2016-11-04 13:45:51 -04:00 committed by Gary Katsevman
parent 74cddcad73
commit fb74c71ba6
6 changed files with 641 additions and 321 deletions

View File

@ -1,168 +1,220 @@
/** /**
* @file text-track-settings.js * @file text-track-settings.js
*/ */
import Component from '../component';
import * as Events from '../utils/events.js';
import * as Fn from '../utils/fn.js';
import log from '../utils/log.js';
import safeParseTuple from 'safe-json-parse/tuple';
import window from 'global/window'; import window from 'global/window';
import Component from '../component';
import {createEl} from '../utils/dom';
import * as Fn from '../utils/fn';
import * as Obj from '../utils/obj';
import log from '../utils/log';
function captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId) { const LOCAL_STORAGE_KEY = 'vjs-text-track-settings';
const template = `
<div role="document">
<div role="heading" aria-level="1" id="${dialogLabelId}" class="vjs-control-text">Captions Settings Dialog</div>
<div id="${dialogDescriptionId}" class="vjs-control-text">Beginning of dialog window. Escape will cancel and close the window.</div>
<div class="vjs-tracksettings">
<div class="vjs-tracksettings-colors">
<fieldset class="vjs-fg-color vjs-tracksetting">
<legend>Text</legend>
<label class="vjs-label" for="captions-foreground-color-${uniqueId}">Color</label>
<select id="captions-foreground-color-${uniqueId}">
<option value="#FFF" selected>White</option>
<option value="#000">Black</option>
<option value="#F00">Red</option>
<option value="#0F0">Green</option>
<option value="#00F">Blue</option>
<option value="#FF0">Yellow</option>
<option value="#F0F">Magenta</option>
<option value="#0FF">Cyan</option>
</select>
<span class="vjs-text-opacity vjs-opacity">
<label class="vjs-label" for="captions-foreground-opacity-${uniqueId}">Transparency</label>
<select id="captions-foreground-opacity-${uniqueId}">
<option value="1" selected>Opaque</option>
<option value="0.5">Semi-Opaque</option>
</select>
</span>
</fieldset>
<fieldset class="vjs-bg-color vjs-tracksetting">
<legend>Background</legend>
<label class="vjs-label" for="captions-background-color-${uniqueId}">Color</label>
<select id="captions-background-color-${uniqueId}">
<option value="#000" selected>Black</option>
<option value="#FFF">White</option>
<option value="#F00">Red</option>
<option value="#0F0">Green</option>
<option value="#00F">Blue</option>
<option value="#FF0">Yellow</option>
<option value="#F0F">Magenta</option>
<option value="#0FF">Cyan</option>
</select>
<span class="vjs-bg-opacity vjs-opacity">
<label class="vjs-label" for="captions-background-opacity-${uniqueId}">Transparency</label>
<select id="captions-background-opacity-${uniqueId}">
<option value="1" selected>Opaque</option>
<option value="0.5">Semi-Transparent</option>
<option value="0">Transparent</option>
</select>
</span>
</fieldset>
<fieldset class="window-color vjs-tracksetting">
<legend>Window</legend>
<label class="vjs-label" for="captions-window-color-${uniqueId}">Color</label>
<select id="captions-window-color-${uniqueId}">
<option value="#000" selected>Black</option>
<option value="#FFF">White</option>
<option value="#F00">Red</option>
<option value="#0F0">Green</option>
<option value="#00F">Blue</option>
<option value="#FF0">Yellow</option>
<option value="#F0F">Magenta</option>
<option value="#0FF">Cyan</option>
</select>
<span class="vjs-window-opacity vjs-opacity">
<label class="vjs-label" for="captions-window-opacity-${uniqueId}">Transparency</label>
<select id="captions-window-opacity-${uniqueId}">
<option value="0" selected>Transparent</option>
<option value="0.5">Semi-Transparent</option>
<option value="1">Opaque</option>
</select>
</span>
</fieldset>
</div> <!-- vjs-tracksettings-colors -->
<div class="vjs-tracksettings-font">
<div class="vjs-font-percent vjs-tracksetting">
<label class="vjs-label" for="captions-font-size-${uniqueId}">Font Size</label>
<select id="captions-font-size-${uniqueId}">
<option value="0.50">50%</option>
<option value="0.75">75%</option>
<option value="1.00" selected>100%</option>
<option value="1.25">125%</option>
<option value="1.50">150%</option>
<option value="1.75">175%</option>
<option value="2.00">200%</option>
<option value="3.00">300%</option>
<option value="4.00">400%</option>
</select>
</div>
<div class="vjs-edge-style vjs-tracksetting">
<label class="vjs-label" for="captions-edge-style-${uniqueId}">Text Edge Style</label>
<select id="captions-edge-style-${uniqueId}">
<option value="none" selected>None</option>
<option value="raised">Raised</option>
<option value="depressed">Depressed</option>
<option value="uniform">Uniform</option>
<option value="dropshadow">Dropshadow</option>
</select>
</div>
<div class="vjs-font-family vjs-tracksetting">
<label class="vjs-label" for="captions-font-family-${uniqueId}">Font Family</label>
<select id="captions-font-family-${uniqueId}">
<option value="proportionalSansSerif" selected>Proportional Sans-Serif</option>
<option value="monospaceSansSerif">Monospace Sans-Serif</option>
<option value="proportionalSerif">Proportional Serif</option>
<option value="monospaceSerif">Monospace Serif</option>
<option value="casual">Casual</option>
<option value="script">Script</option>
<option value="small-caps">Small Caps</option>
</select>
</div>
</div> <!-- vjs-tracksettings-font -->
<div class="vjs-tracksettings-controls">
<button class="vjs-default-button">Defaults</button>
<button class="vjs-done-button">Done</button>
</div>
</div> <!-- vjs-tracksettings -->
</div> <!-- role="document" -->
`;
return template; const COLOR_BLACK = ['#000', 'Black'];
} const COLOR_BLUE = ['#00F', 'Blue'];
const COLOR_CYAN = ['#0FF', 'Cyan'];
const COLOR_GREEN = ['#0F0', 'Green'];
const COLOR_MAGENTA = ['#F0F', 'Magenta'];
const COLOR_RED = ['#F00', 'Red'];
const COLOR_WHITE = ['#FFF', 'White'];
const COLOR_YELLOW = ['#FF0', 'Yellow'];
function getSelectedOptionValue(target) { const OPACITY_OPAQUE = ['1', 'Opaque'];
let selectedOption; const OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
const OPACITY_TRANS = ['0', 'Transparent'];
// not all browsers support selectedOptions, so, fallback to options // Configuration for the various <select> elements in the DOM of this component.
if (target.selectedOptions) { //
selectedOption = target.selectedOptions[0]; // Possible keys include:
} else if (target.options) { //
selectedOption = target.options[target.options.selectedIndex]; // `default`:
// The default option index. Only needs to be provided if not zero.
// `parser`:
// A function which is used to parse the value from the selected option in
// a customized way.
// `selector`:
// The selector used to find the associated <select> element.
const selectConfigs = {
backgroundColor: {
selector: '.vjs-bg-color > select',
id: 'captions-background-color-%s',
label: 'Color',
options: [
COLOR_BLACK,
COLOR_WHITE,
COLOR_RED,
COLOR_GREEN,
COLOR_BLUE,
COLOR_YELLOW,
COLOR_MAGENTA,
COLOR_CYAN
]
},
backgroundOpacity: {
selector: '.vjs-bg-opacity > select',
id: 'captions-background-opacity-%s',
label: 'Transparency',
options: [
OPACITY_OPAQUE,
OPACITY_SEMI,
OPACITY_TRANS
]
},
color: {
selector: '.vjs-fg-color > select',
id: 'captions-foreground-color-%s',
label: 'Color',
options: [
COLOR_WHITE,
COLOR_BLACK,
COLOR_RED,
COLOR_GREEN,
COLOR_BLUE,
COLOR_YELLOW,
COLOR_MAGENTA,
COLOR_CYAN
]
},
edgeStyle: {
selector: '.vjs-edge-style > select',
id: '%s',
label: 'Text Edge Style',
options: [
['none', 'None'],
['raised', 'Raised'],
['depressed', 'Depressed'],
['uniform', 'Uniform'],
['dropshadow', 'Dropshadow']
]
},
fontFamily: {
selector: '.vjs-font-family > select',
id: 'captions-font-family-%s',
label: 'Font Family',
options: [
['proportionalSansSerif', 'Proportional Sans-Serif'],
['monospaceSansSerif', 'Monospace Sans-Serif'],
['proportionalSerif', 'Proportional Serif'],
['monospaceSerif', 'Monospace Serif'],
['casual', 'Casual'],
['script', 'Script'],
['small-caps', 'Small Caps']
]
},
fontPercent: {
selector: '.vjs-font-percent > select',
id: 'captions-font-size-%s',
label: 'Font Size',
options: [
['0.50', '50%'],
['0.75', '75%'],
['1.00', '100%'],
['1.25', '125%'],
['1.50', '150%'],
['1.75', '175%'],
['2.00', '200%'],
['3.00', '300%'],
['4.00', '400%']
],
default: 2,
parser: (v) => v === '1.00' ? null : Number(v)
},
textOpacity: {
selector: '.vjs-text-opacity > select',
id: 'captions-foreground-opacity-%s',
label: 'Transparency',
options: [
OPACITY_OPAQUE,
OPACITY_SEMI
]
},
// Options for this object are defined below.
windowColor: {
selector: '.vjs-window-color > select',
id: 'captions-window-color-%s',
label: 'Color'
},
// Options for this object are defined below.
windowOpacity: {
selector: '.vjs-window-opacity > select',
id: 'captions-window-opacity-%s',
label: 'Transparency',
options: [
OPACITY_TRANS,
OPACITY_SEMI,
OPACITY_OPAQUE
]
}
};
selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
/**
* Parses out option values.
*
* @private
* @param {String} value
* @param {Function} [parser]
* Optional function to adjust the value.
* @return {Mixed}
* Will be `undefined` if no value exists (or if given value is "none").
*/
function parseOptionValue(value, parser) {
if (parser) {
value = parser(value);
} }
return selectedOption.value; if (value && value !== 'none') {
return value;
}
} }
function setSelectedOption(target, value) { /**
* Gets the value of the selected <option> element within a <select> element.
*
* @param {Object} config
* @param {Function} [parser]
* Optional function to adjust the value.
* @return {Mixed}
*/
function getSelectedOptionValue(el, parser) {
const value = el.options[el.options.selectedIndex].value;
return parseOptionValue(value, parser);
}
/**
* Sets the selected <option> element within a <select> element based on a
* given value.
*
* @param {Object} el
* @param {String} value
* @param {Function} [parser]
* Optional function to adjust the value before comparing.
*/
function setSelectedOption(el, value, parser) {
if (!value) { if (!value) {
return; return;
} }
let i; for (let i = 0; i < el.options.length; i++) {
if (parseOptionValue(el.options[i].value, parser) === value) {
for (i = 0; i < target.options.length; i++) { el.selectedIndex = i;
const option = target.options[i];
if (option.value === value) {
break; break;
} }
} }
target.selectedIndex = i;
} }
/** /**
* Manipulate settings of texttracks * Manipulate settings of text tracks
* *
* @param {Object} player Main Player * @param {Object} player Main Player
* @param {Object=} options Object of option names and values * @param {Object=} options Object of option names and values
@ -173,46 +225,196 @@ class TextTrackSettings extends Component {
constructor(player, options) { constructor(player, options) {
super(player, options); super(player, options);
this.setDefaults();
this.hide(); this.hide();
this.updateDisplay = Fn.bind(this, this.updateDisplay);
// Grab `persistTextTrackSettings` from the player options if not passed in child options // Grab `persistTextTrackSettings` from the player options if not passed in child options
if (options.persistTextTrackSettings === undefined) { if (options.persistTextTrackSettings === undefined) {
this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings; this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings;
} }
Events.on(this.$('.vjs-done-button'), 'click', Fn.bind(this, function() { this.on(this.$('.vjs-done-button'), 'click', () => {
this.saveSettings(); this.saveSettings();
this.hide(); this.hide();
})); });
Events.on(this.$('.vjs-default-button'), 'click', Fn.bind(this, function() { this.on(this.$('.vjs-default-button'), 'click', () => {
this.$('.vjs-fg-color > select').selectedIndex = 0; this.setDefaults();
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(); this.updateDisplay();
})); });
Events.on(this.$('.vjs-fg-color > select'), 'change', Fn.bind(this, this.updateDisplay)); Obj.each(selectConfigs, config => {
Events.on(this.$('.vjs-bg-color > select'), 'change', Fn.bind(this, this.updateDisplay)); this.on(this.$(config.selector), 'change', 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) { if (this.options_.persistTextTrackSettings) {
this.restoreSettings(); this.restoreSettings();
} }
} }
/**
* Create a <select> element with configured options.
*
* @private
* @return {Element}
* @method createElSelect_
*/
createElSelect_(key) {
const config = selectConfigs[key];
const id = config.id.replace('%s', this.id_);
return [
createEl('label', {
className: 'vjs-label',
textContent: config.label
}, {
for: id
}),
createEl('select', {id}, undefined, config.options.map(o => {
return createEl('option', {
textContent: this.localize(o[1]),
value: o[0]
});
}))
];
}
/**
* Create foreground color element for the component
*
* @private
* @return {Element}
* @method createElFgColor_
*/
createElFgColor_() {
const legend = createEl('legend', {
textContent: this.localize('Text')
});
const select = this.createElSelect_('color');
const opacity = createEl('span', {
className: 'vjs-text-opacity vjs-opacity'
}, undefined, this.createElSelect_('textOpacity'));
return createEl('fieldset', {
className: 'vjs-fg-color vjs-tracksetting'
}, undefined, [legend].concat(select, opacity));
}
/**
* Create background color element for the component
*
* @private
* @return {Element}
* @method createElBgColor_
*/
createElBgColor_() {
const legend = createEl('legend', {
textContent: this.localize('Background')
});
const select = this.createElSelect_('backgroundColor');
const opacity = createEl('span', {
className: 'vjs-bg-opacity vjs-opacity'
}, undefined, this.createElSelect_('backgroundOpacity'));
return createEl('fieldset', {
className: 'vjs-bg-color vjs-tracksetting'
}, undefined, [legend].concat(select, opacity));
}
/**
* Create window color element for the component
*
* @private
* @return {Element}
* @method createElWinColor_
*/
createElWinColor_() {
const legend = createEl('legend', {
textContent: this.localize('Window')
});
const select = this.createElSelect_('windowColor');
const opacity = createEl('span', {
className: 'vjs-window-opacity vjs-opacity'
}, undefined, this.createElSelect_('windowOpacity'));
return createEl('fieldset', {
className: 'vjs-window-color vjs-tracksetting'
}, undefined, [legend].concat(select, opacity));
}
/**
* Create color elements for the component
*
* @private
* @return {Element}
* @method createElColors_
*/
createElColors_() {
return createEl('div', {
className: 'vjs-tracksettings-colors'
}, undefined, [
this.createElFgColor_(),
this.createElBgColor_(),
this.createElWinColor_()
]);
}
/**
* Create font elements for the component
*
* @private
* @return {Element}
* @method createElFont_
*/
createElFont_() {
const fontPercent = createEl('div', {
className: 'vjs-font-percent vjs-tracksetting'
}, undefined, this.createElSelect_('fontPercent'));
const edgeStyle = createEl('div', {
className: 'vjs-edge-style vjs-tracksetting'
}, undefined, this.createElSelect_('edgeStyle'));
const fontFamily = createEl('div', {
className: 'vjs-font-family vjs-tracksetting'
}, undefined, this.createElSelect_('fontFamily'));
return createEl('div', {
className: 'vjs-tracksettings-font'
}, undefined, [fontPercent, edgeStyle, fontFamily]);
}
/**
* Create controls for the component
*
* @private
* @return {Element}
* @method createElControls_
*/
createElControls_() {
const defaultsButton = createEl('button', {
className: 'vjs-default-button',
textContent: this.localize('Defaults')
});
const doneButton = createEl('button', {
className: 'vjs-done-button',
textContent: 'Done'
});
return createEl('div', {
className: 'vjs-tracksettings-controls'
}, undefined, [defaultsButton, doneButton]);
}
/** /**
* Create the component's DOM element * Create the component's DOM element
* *
@ -220,99 +422,86 @@ class TextTrackSettings extends Component {
* @method createEl * @method createEl
*/ */
createEl() { createEl() {
const uniqueId = this.id_; const settings = createEl('div', {
const dialogLabelId = 'TTsettingsDialogLabel-' + uniqueId; className: 'vjs-tracksettings'
const dialogDescriptionId = 'TTsettingsDialogDescription-' + uniqueId; }, undefined, [
this.createElColors_(),
this.createElFont_(),
this.createElControls_()
]);
return super.createEl('div', { const heading = createEl('div', {
className: 'vjs-control-text',
id: `TTsettingsDialogLabel-${this.id_}`,
textContent: 'Caption Settings Dialog'
}, {
'aria-level': '1',
'role': 'heading'
});
const description = createEl('div', {
className: 'vjs-control-text',
id: `TTsettingsDialogDescription-${this.id_}`,
textContent: 'Beginning of dialog window. Escape will cancel and close the window.'
});
const doc = createEl('div', undefined, {
role: 'document'
}, [heading, description, settings]);
return createEl('div', {
className: 'vjs-caption-settings vjs-modal-overlay', className: 'vjs-caption-settings vjs-modal-overlay',
innerHTML: captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId),
tabIndex: -1 tabIndex: -1
}, { }, {
'role': 'dialog', 'role': 'dialog',
'aria-labelledby': dialogLabelId, 'aria-labelledby': heading.id,
'aria-describedby': dialogDescriptionId 'aria-describedby': description.id
}, doc);
}
/**
* Gets an object of text track settings (or null).
*
* @return {Object}
* An object with config values parsed from the DOM or localStorage.
* @method getValues
*/
getValues() {
return Obj.reduce(selectConfigs, (accum, config, key) => {
const value = getSelectedOptionValue(this.$(config.selector), config.parser);
if (value !== undefined) {
accum[key] = value;
}
return accum;
}, {});
}
/**
* Sets text track settings from an object of values.
*
* @param {Object} values
* An object with config values parsed from the DOM or localStorage.
* @method setValues
*/
setValues(values) {
Obj.each(selectConfigs, (config, key) => {
setSelectedOption(this.$(config.selector), values[key], config.parser);
}); });
} }
/** /**
* Get texttrack settings * Sets all <select> elements to their default values.
* Settings are
* .vjs-edge-style
* .vjs-font-family
* .vjs-fg-color
* .vjs-text-opacity
* .vjs-bg-color
* .vjs-bg-opacity
* .window-color
* .vjs-window-opacity
* *
* @return {Object} * @method setDefaults
* @method getValues
*/ */
getValues() { setDefaults() {
const textEdge = getSelectedOptionValue(this.$('.vjs-edge-style select')); Obj.each(selectConfigs, (config) => {
const fontFamily = getSelectedOptionValue(this.$('.vjs-font-family select')); const index = config.hasOwnProperty('default') ? config.default : 0;
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')));
const result = { this.$(config.selector).selectedIndex = index;
fontPercent, });
fontFamily,
textOpacity,
windowColor,
windowOpacity,
backgroundOpacity: bgOpacity,
edgeStyle: textEdge,
color: fgColor,
backgroundColor: bgColor
};
for (const name in result) {
if (result[name] === '' || result[name] === 'none' || (name === 'fontPercent' && result[name] === 1.00)) {
delete result[name];
}
}
return result;
}
/**
* Set texttrack settings
* Settings are
* .vjs-edge-style
* .vjs-font-family
* .vjs-fg-color
* .vjs-text-opacity
* .vjs-bg-color
* .vjs-bg-opacity
* .window-color
* .vjs-window-opacity
*
* @param {Object} values Object with texttrack setting values
* @method setValues
*/
setValues(values) {
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;
if (fontPercent) {
fontPercent = fontPercent.toFixed(2);
}
setSelectedOption(this.$('.vjs-font-percent > select'), fontPercent);
} }
/** /**
@ -321,17 +510,12 @@ class TextTrackSettings extends Component {
* @method restoreSettings * @method restoreSettings
*/ */
restoreSettings() { restoreSettings() {
let err;
let values; let values;
try { try {
[err, values] = safeParseTuple(window.localStorage.getItem('vjs-text-track-settings')); values = JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_KEY));
} catch (err) {
if (err) { log.warn(err);
log.error(err);
}
} catch (e) {
log.warn(e);
} }
if (values) { if (values) {
@ -340,7 +524,7 @@ class TextTrackSettings extends Component {
} }
/** /**
* Save texttrack settings to local storage * Save text track settings to local storage
* *
* @method saveSettings * @method saveSettings
*/ */
@ -352,18 +536,18 @@ class TextTrackSettings extends Component {
const values = this.getValues(); const values = this.getValues();
try { try {
if (Object.getOwnPropertyNames(values).length > 0) { if (Object.keys(values).length) {
window.localStorage.setItem('vjs-text-track-settings', JSON.stringify(values)); window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
} else { } else {
window.localStorage.removeItem('vjs-text-track-settings'); window.localStorage.removeItem(LOCAL_STORAGE_KEY);
} }
} catch (e) { } catch (err) {
log.warn(e); log.warn(err);
} }
} }
/** /**
* Update display of texttrack settings * Update display of text track settings
* *
* @method updateDisplay * @method updateDisplay
*/ */

View File

@ -96,10 +96,11 @@ export function getEl(id) {
* @param {String} [tagName='div'] Name of tag to be created. * @param {String} [tagName='div'] Name of tag to be created.
* @param {Object} [properties={}] Element properties to be applied. * @param {Object} [properties={}] Element properties to be applied.
* @param {Object} [attributes={}] Element attributes to be applied. * @param {Object} [attributes={}] Element attributes to be applied.
* @param {String|Element|TextNode|Array|Function} [content] Contents for the element (see: `normalizeContent`)
* @return {Element} * @return {Element}
* @function createEl * @function createEl
*/ */
export function createEl(tagName = 'div', properties = {}, attributes = {}) { export function createEl(tagName = 'div', properties = {}, attributes = {}, content) {
const el = document.createElement(tagName); const el = document.createElement(tagName);
Object.getOwnPropertyNames(properties).forEach(function(propName) { Object.getOwnPropertyNames(properties).forEach(function(propName) {
@ -113,6 +114,11 @@ export function createEl(tagName = 'div', properties = {}, attributes = {}) {
has been deprecated. Use the third argument instead. has been deprecated. Use the third argument instead.
createEl(type, properties, attributes). Attempting to set ${propName} to ${val}.`); createEl(type, properties, attributes). Attempting to set ${propName} to ${val}.`);
el.setAttribute(propName, val); el.setAttribute(propName, val);
// Handle textContent since it's not supported everywhere and we have a
// method for it.
} else if (propName === 'textContent') {
textContent(el, val);
} else { } else {
el[propName] = val; el[propName] = val;
} }
@ -122,6 +128,10 @@ export function createEl(tagName = 'div', properties = {}, attributes = {}) {
el.setAttribute(attrName, attributes[attrName]); el.setAttribute(attrName, attributes[attrName]);
}); });
if (content) {
appendContent(el, content);
}
return el; return el;
} }
@ -139,6 +149,7 @@ export function textContent(el, text) {
} else { } else {
el.textContent = text; el.textContent = text;
} }
return el;
} }
/** /**

32
src/js/utils/obj.js Normal file
View File

@ -0,0 +1,32 @@
/**
* @file obj.js
*/
/**
* Array-like iteration for objects.
*
* @param {Object} object
* @param {Function} fn
* A callback function which is called for each key in the object. It
* receives the value and key as arguments.
*/
export function each(object, fn) {
Object.keys(object).forEach(key => fn(object[key], key));
}
/**
* Array-like reduce for objects.
*
* @param {Object} object
* @param {Function} fn
* A callback function which is called for each key in the object. It
* receives the accumulated value and the per-iteration value and key
* as arguments.
* @param {Mixed} [initial = 0]
* @return {Mixed}
*/
export function reduce(object, fn, initial = 0) {
return Object.keys(object).reduce(
(accum, key) => fn(accum, object[key], key),
initial);
}

View File

@ -31,6 +31,7 @@ QUnit.test('should update settings', function(assert) {
tracks, tracks,
persistTextTrackSettings: true persistTextTrackSettings: true
}); });
const newSettings = { const newSettings = {
backgroundOpacity: '0.5', backgroundOpacity: '0.5',
textOpacity: '0.5', textOpacity: '0.5',
@ -44,43 +45,52 @@ QUnit.test('should update settings', function(assert) {
}; };
player.textTrackSettings.setValues(newSettings); player.textTrackSettings.setValues(newSettings);
assert.deepEqual(player.textTrackSettings.getValues(), assert.deepEqual(player.textTrackSettings.getValues(),
newSettings, newSettings,
'values are updated'); 'values are updated');
assert.equal(player.$('.vjs-fg-color > select').selectedIndex, assert.equal(player.$('.vjs-fg-color > select').selectedIndex,
2, 2,
'fg-color is set to new value'); 'fg-color is set to new value');
assert.equal(player.$('.vjs-bg-color > select').selectedIndex, assert.equal(player.$('.vjs-bg-color > select').selectedIndex,
1, 1,
'bg-color is set to new value'); 'bg-color is set to new value');
assert.equal(player.$('.window-color > select').selectedIndex,
1, assert.equal(player.$('.vjs-window-color > select').selectedIndex,
'window-color is set to new value'); 1,
'window-color is set to new value');
assert.equal(player.$('.vjs-text-opacity > select').selectedIndex, assert.equal(player.$('.vjs-text-opacity > select').selectedIndex,
1, 1,
'text-opacity is set to new value'); 'text-opacity is set to new value');
assert.equal(player.$('.vjs-bg-opacity > select').selectedIndex, assert.equal(player.$('.vjs-bg-opacity > select').selectedIndex,
1, 1,
'bg-opacity is set to new value'); 'bg-opacity is set to new value');
assert.equal(player.$('.vjs-window-opacity > select').selectedIndex, assert.equal(player.$('.vjs-window-opacity > select').selectedIndex,
1, 1,
'window-opacity is set to new value'); 'window-opacity is set to new value');
assert.equal(player.$('.vjs-edge-style select').selectedIndex, assert.equal(player.$('.vjs-edge-style select').selectedIndex,
1, 1,
'edge-style is set to new value'); 'edge-style is set to new value');
assert.equal(player.$('.vjs-font-family select').selectedIndex, assert.equal(player.$('.vjs-font-family select').selectedIndex,
3, 3,
'font-family is set to new value'); 'font-family is set to new value');
assert.equal(player.$('.vjs-font-percent select').selectedIndex, assert.equal(player.$('.vjs-font-percent select').selectedIndex,
3, 3,
'font-percent is set to new value'); 'font-percent is set to new value');
Events.trigger(player.$('.vjs-done-button'), 'click'); Events.trigger(player.$('.vjs-done-button'), 'click');
assert.deepEqual(safeParseTuple(
window.localStorage.getItem('vjs-text-track-settings'))[1], assert.deepEqual(safeParseTuple(window.localStorage.getItem('vjs-text-track-settings'))[1],
newSettings, newSettings,
'values are saved'); 'values are saved');
player.dispose(); player.dispose();
}); });
@ -93,7 +103,7 @@ QUnit.test('should restore default settings', function(assert) {
player.$('.vjs-fg-color > select').selectedIndex = 1; player.$('.vjs-fg-color > select').selectedIndex = 1;
player.$('.vjs-bg-color > select').selectedIndex = 1; player.$('.vjs-bg-color > select').selectedIndex = 1;
player.$('.window-color > select').selectedIndex = 1; player.$('.vjs-window-color > select').selectedIndex = 1;
player.$('.vjs-text-opacity > select').selectedIndex = 1; player.$('.vjs-text-opacity > select').selectedIndex = 1;
player.$('.vjs-bg-opacity > select').selectedIndex = 1; player.$('.vjs-bg-opacity > select').selectedIndex = 1;
player.$('.vjs-window-opacity > select').selectedIndex = 1; player.$('.vjs-window-opacity > select').selectedIndex = 1;
@ -106,8 +116,8 @@ QUnit.test('should restore default settings', function(assert) {
Events.trigger(player.$('.vjs-done-button'), 'click'); Events.trigger(player.$('.vjs-done-button'), 'click');
assert.deepEqual(player.textTrackSettings.getValues(), assert.deepEqual(player.textTrackSettings.getValues(),
defaultSettings, defaultSettings,
'values are defaulted'); 'values are defaulted');
// TODO: // TODO:
// MikeA: need to figure out how to modify saveSettings // MikeA: need to figure out how to modify saveSettings
// to factor in defaults are no longer null // to factor in defaults are no longer null
@ -116,32 +126,40 @@ QUnit.test('should restore default settings', function(assert) {
// 'values are saved'); // 'values are saved');
assert.equal(player.$('.vjs-fg-color > select').selectedIndex, assert.equal(player.$('.vjs-fg-color > select').selectedIndex,
0, 0,
'fg-color is set to default value'); 'fg-color is set to default value');
assert.equal(player.$('.vjs-bg-color > select').selectedIndex, assert.equal(player.$('.vjs-bg-color > select').selectedIndex,
0, 0,
'bg-color is set to default value'); 'bg-color is set to default value');
assert.equal(player.$('.window-color > select').selectedIndex,
0, assert.equal(player.$('.vjs-window-color > select').selectedIndex,
'window-color is set to default value'); 0,
'window-color is set to default value');
assert.equal(player.$('.vjs-text-opacity > select').selectedIndex, assert.equal(player.$('.vjs-text-opacity > select').selectedIndex,
0, 0,
'text-opacity is set to default value'); 'text-opacity is set to default value');
assert.equal(player.$('.vjs-bg-opacity > select').selectedIndex, assert.equal(player.$('.vjs-bg-opacity > select').selectedIndex,
0, 0,
'bg-opacity is set to default value'); 'bg-opacity is set to default value');
assert.equal(player.$('.vjs-window-opacity > select').selectedIndex, assert.equal(player.$('.vjs-window-opacity > select').selectedIndex,
0, 0,
'window-opacity is set to default value'); 'window-opacity is set to default value');
assert.equal(player.$('.vjs-edge-style select').selectedIndex, assert.equal(player.$('.vjs-edge-style select').selectedIndex,
0, 0,
'edge-style is set to default value'); 'edge-style is set to default value');
assert.equal(player.$('.vjs-font-family select').selectedIndex, assert.equal(player.$('.vjs-font-family select').selectedIndex,
0, 0,
'font-family is set to default value'); 'font-family is set to default value');
assert.equal(player.$('.vjs-font-percent select').selectedIndex, assert.equal(player.$('.vjs-font-percent select').selectedIndex,
2, 2,
'font-percent is set to default value'); 'font-percent is set to default value');
player.dispose(); player.dispose();
}); });

View File

@ -15,8 +15,8 @@ QUnit.test('should return the element with the ID', function(assert) {
el1.id = 'test_id1'; el1.id = 'test_id1';
el2.id = 'test_id2'; el2.id = 'test_id2';
assert.ok(Dom.getEl('test_id1') === el1, 'found element for ID'); assert.strictEqual(Dom.getEl('test_id1'), el1, 'found element for ID');
assert.ok(Dom.getEl('#test_id2') === el2, 'found element for CSS ID'); assert.strictEqual(Dom.getEl('#test_id2'), el2, 'found element for CSS ID');
}); });
QUnit.test('should create an element', function(assert) { QUnit.test('should create an element', function(assert) {
@ -27,10 +27,27 @@ QUnit.test('should create an element', function(assert) {
'data-test': 'asdf' 'data-test': 'asdf'
}); });
assert.ok(div.nodeName === 'DIV'); assert.strictEqual(div.nodeName, 'DIV');
assert.ok(span.nodeName === 'SPAN'); assert.strictEqual(span.nodeName, 'SPAN');
assert.ok(span.getAttribute('data-test') === 'asdf'); assert.strictEqual(span.getAttribute('data-test'), 'asdf');
assert.ok(span.innerHTML === 'fdsa'); assert.strictEqual(span.innerHTML, 'fdsa');
});
QUnit.test('should create an element, supporting textContent', function(assert) {
const span = Dom.createEl('span', {textContent: 'howdy'});
if (span.textContent) {
assert.strictEqual(span.textContent, 'howdy', 'works in browsers that support textContent');
} else {
assert.strictEqual(span.innerText, 'howdy', 'works in browsers that DO NOT support textContent');
}
});
QUnit.test('should create an element with content', function(assert) {
const span = Dom.createEl('span');
const div = Dom.createEl('div', undefined, undefined, span);
assert.strictEqual(div.firstChild, span);
}); });
QUnit.test('should insert an element first in another', function(assert) { QUnit.test('should insert an element first in another', function(assert) {
@ -39,28 +56,28 @@ QUnit.test('should insert an element first in another', function(assert) {
const parent = document.createElement('div'); const parent = document.createElement('div');
Dom.insertElFirst(el1, parent); Dom.insertElFirst(el1, parent);
assert.ok(parent.firstChild === el1, 'inserts first into empty parent'); assert.strictEqual(parent.firstChild, el1, 'inserts first into empty parent');
Dom.insertElFirst(el2, parent); Dom.insertElFirst(el2, parent);
assert.ok(parent.firstChild === el2, 'inserts first into parent with child'); assert.strictEqual(parent.firstChild, el2, 'inserts first into parent with child');
}); });
QUnit.test('should get and remove data from an element', function(assert) { QUnit.test('should get and remove data from an element', function(assert) {
const el = document.createElement('div'); const el = document.createElement('div');
const data = Dom.getElData(el); const data = Dom.getElData(el);
assert.ok(typeof data === 'object', 'data object created'); assert.strictEqual(typeof data, 'object', 'data object created');
// Add data // Add data
const testData = { asdf: 'fdsa' }; const testData = {asdf: 'fdsa'};
data.test = testData; data.test = testData;
assert.ok(Dom.getElData(el).test === testData, 'data added'); assert.strictEqual(Dom.getElData(el).test, testData, 'data added');
// Remove all data // Remove all data
Dom.removeElData(el); Dom.removeElData(el);
assert.ok(!Dom.hasElData(el), 'cached item emptied'); assert.notOk(Dom.hasElData(el), 'cached item emptied');
}); });
QUnit.test('addElClass()', function(assert) { QUnit.test('addElClass()', function(assert) {

View File

@ -0,0 +1,58 @@
/* eslint-env qunit */
import sinon from 'sinon';
import * as Obj from '../../../src/js/utils/obj';
QUnit.module('utils/obj');
QUnit.test('each', function(assert) {
const spy = sinon.spy();
Obj.each({
a: 1,
b: 'foo',
c: null
}, spy);
assert.strictEqual(spy.callCount, 3);
assert.ok(spy.calledWith(1, 'a'));
assert.ok(spy.calledWith('foo', 'b'));
assert.ok(spy.calledWith(null, 'c'));
Obj.each({}, spy);
assert.strictEqual(spy.callCount, 3, 'an empty object was not iterated over');
});
QUnit.test('reduce', function(assert) {
const first = Obj.reduce({
a: 1,
b: 2,
c: 3,
d: 4
}, (accum, value) => accum + value);
assert.strictEqual(first, 10);
const second = Obj.reduce({
a: 1,
b: 2,
c: 3,
d: 4
}, (accum, value) => accum + value, 10);
assert.strictEqual(second, 20);
const third = Obj.reduce({
a: 1,
b: 2,
c: 3,
d: 4
}, (accum, value, key) => {
accum[key] = 0 - value;
return accum;
}, {});
assert.strictEqual(third.a, -1);
assert.strictEqual(third.b, -2);
assert.strictEqual(third.c, -3);
assert.strictEqual(third.d, -4);
});