Skip to content

Commit

Permalink
feat: addClass and removeClass method supports adding/removing multip…
Browse files Browse the repository at this point in the history
…le classes (#7798)
  • Loading branch information
gjanblaszczyk authored and misteroneill committed Nov 23, 2022
1 parent 63a2d02 commit 9b3d941
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 97 deletions.
16 changes: 8 additions & 8 deletions src/js/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -815,21 +815,21 @@ class Component {
/**
* Add a CSS class name to the `Component`s element.
*
* @param {string} classToAdd
* CSS class name to add
* @param {...string} classesToAdd
* One or more CSS class name to add.
*/
addClass(classToAdd) {
Dom.addClass(this.el_, classToAdd);
addClass(...classesToAdd) {
Dom.addClass(this.el_, ...classesToAdd);
}

/**
* Remove a CSS class name from the `Component`s element.
*
* @param {string} classToRemove
* CSS class name to remove
* @param {...string} classesToRemove
* One or more CSS class name to remove.
*/
removeClass(classToRemove) {
Dom.removeClass(this.el_, classToRemove);
removeClass(...classesToRemove) {
Dom.removeClass(this.el_, ...classesToRemove);
}

/**
Expand Down
5 changes: 1 addition & 4 deletions src/js/control-bar/mute-toggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,7 @@ class MuteToggle extends Button {
level = 2;
}

// TODO improve muted icon classes
for (let i = 0; i < 4; i++) {
Dom.removeClass(this.el_, `vjs-vol-${i}`);
}
Dom.removeClass(this.el_, [0, 1, 2, 3].reduce((str, i) => str + `${i ? ' ' : ''}vjs-vol-${i}`, ''));
Dom.addClass(this.el_, `vjs-vol-${level}`);
}

Expand Down
3 changes: 1 addition & 2 deletions src/js/control-bar/play-toggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ class PlayToggle extends Button {
* @listens Player#play
*/
handlePlay(event) {
this.removeClass('vjs-ended');
this.removeClass('vjs-paused');
this.removeClass('vjs-ended', 'vjs-paused');
this.addClass('vjs-playing');
// change the button text to "Pause"
this.controlText('Pause');
Expand Down
9 changes: 3 additions & 6 deletions src/js/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -1384,8 +1384,7 @@ class Player extends Component {
handleTechLoadStart_() {
// TODO: Update to use `emptied` event instead. See #1277.

this.removeClass('vjs-ended');
this.removeClass('vjs-seeking');
this.removeClass('vjs-ended', 'vjs-seeking');

// reset the error state
this.error(null);
Expand Down Expand Up @@ -1662,8 +1661,7 @@ class Player extends Component {
* @private
*/
handleTechPlay_() {
this.removeClass('vjs-ended');
this.removeClass('vjs-paused');
this.removeClass('vjs-ended', 'vjs-paused');
this.addClass('vjs-playing');

// hide the poster when the user hits play
Expand Down Expand Up @@ -1816,8 +1814,7 @@ class Player extends Component {
* @private
*/
handleTechSeeked_() {
this.removeClass('vjs-seeking');
this.removeClass('vjs-ended');
this.removeClass('vjs-seeking', 'vjs-ended');
/**
* Fired when the player has finished jumping to a new time
*
Expand Down
3 changes: 1 addition & 2 deletions src/js/tracks/text-track-display.js
Original file line number Diff line number Diff line change
Expand Up @@ -418,8 +418,7 @@ class TextTrackDisplay extends Component {
for (let j = 0; j < track.activeCues.length; ++j) {
const cueEl = track.activeCues[j].displayState;

Dom.addClass(cueEl, 'vjs-text-track-cue');
Dom.addClass(cueEl, 'vjs-text-track-cue-' + ((track.language) ? track.language : i));
Dom.addClass(cueEl, 'vjs-text-track-cue', 'vjs-text-track-cue-' + ((track.language) ? track.language : i));
if (track.language) {
Dom.setAttribute(cueEl, 'lang', track.language);
}
Expand Down
73 changes: 12 additions & 61 deletions src/js/utils/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,6 @@ function throwIfWhitespace(str) {
}
}

/**
* Produce a regular expression for matching a className within an elements className.
*
* @private
* @param {string} className
* The className to generate the RegExp for.
*
* @return {RegExp}
* The RegExp that will check for a specific `className` in an elements
* className.
*/
function classRegExp(className) {
return new RegExp('(^|\\s)' + className + '($|\\s)');
}

/**
* Whether the current DOM interface appears to be real (i.e. not simulated).
*
Expand Down Expand Up @@ -228,10 +213,8 @@ export function prependTo(child, parent) {
*/
export function hasClass(element, classToCheck) {
throwIfWhitespace(classToCheck);
if (element.classList) {
return element.classList.contains(classToCheck);
}
return classRegExp(classToCheck).test(element.className);

return element.classList.contains(classToCheck);
}

/**
Expand All @@ -240,21 +223,14 @@ export function hasClass(element, classToCheck) {
* @param {Element} element
* Element to add class name to.
*
* @param {string} classToAdd
* Class name to add.
* @param {...string} classesToAdd
* One or more class name to add.
*
* @return {Element}
* The DOM element with the added class name.
*/
export function addClass(element, 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 (!hasClass(element, classToAdd)) {
element.className = (element.className + ' ' + classToAdd).trim();
}
export function addClass(element, ...classesToAdd) {
element.classList.add(...classesToAdd.reduce((prev, current) => prev.concat(current.split(/\s+/)), []));

return element;
}
Expand All @@ -265,26 +241,19 @@ export function addClass(element, classToAdd) {
* @param {Element} element
* Element to remove a class name from.
*
* @param {string} classToRemove
* Class name to remove
* @param {...string} classesToRemove
* One or more class name to remove.
*
* @return {Element}
* The DOM element with class name removed.
*/
export function removeClass(element, classToRemove) {
export function removeClass(element, ...classesToRemove) {
// Protect in case the player gets disposed
if (!element) {
log.warn("removeClass was called with an element that doesn't exist");
return null;
}
if (element.classList) {
element.classList.remove(classToRemove);
} else {
throwIfWhitespace(classToRemove);
element.className = element.className.split(/\s+/).filter(function(c) {
return c !== classToRemove;
}).join(' ');
}
element.classList.remove(...classesToRemove.reduce((prev, current) => prev.concat(current.split(/\s+/)), []));

return element;
}
Expand Down Expand Up @@ -322,31 +291,13 @@ export function removeClass(element, classToRemove) {
* The element with a class that has been toggled.
*/
export function toggleClass(element, classToToggle, predicate) {

// This CANNOT use `classList` internally because IE11 does not support the
// second parameter to the `classList.toggle()` method! Which is fine because
// `classList` will be used by the add/remove functions.
const has = hasClass(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) {
addClass(element, classToToggle);
} else {
removeClass(element, classToToggle);
predicate = undefined;
}
classToToggle.split(/\s+/).forEach(className => element.classList.toggle(className, predicate));

return element;
}
Expand Down
35 changes: 35 additions & 0 deletions test/unit/component.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,41 @@ QUnit.test('should add and remove a CSS class', function(assert) {
comp.dispose();
});

QUnit.test('should add and remove CSS classes', function(assert) {
const comp = new Component(this.player, {});

comp.addClass('first-class', 'second-class');
assert.ok(comp.el().className.indexOf('first-class') !== -1);
assert.ok(comp.el().className.indexOf('second-class') !== -1);
comp.removeClass('first-class', 'second-class');
assert.ok(comp.el().className.indexOf('first-class') === -1);
assert.ok(comp.el().className.indexOf('second-class') === -1);

comp.addClass('first-class second-class');
assert.ok(comp.el().className.indexOf('first-class') !== -1);
assert.ok(comp.el().className.indexOf('second-class') !== -1);
comp.removeClass('first-class second-class');
assert.ok(comp.el().className.indexOf('first-class') === -1);
assert.ok(comp.el().className.indexOf('second-class') === -1);

comp.addClass('be cool', 'scooby', 'doo');
assert.ok(comp.el().className.indexOf('be cool scooby doo') !== -1);
comp.removeClass('be cool', 'scooby', 'doo');
assert.ok(comp.el().className.indexOf('be cool scooby doo') === -1);

comp.addClass('multiple spaces between words');
assert.ok(comp.el().className.indexOf('multiple spaces between words') !== -1);
comp.removeClass('multiple spaces between words');
assert.ok(comp.el().className.indexOf('multiple spaces between words') === -1);

comp.toggleClass('first-class second-class');
assert.ok(comp.el().className.indexOf('first-class second-class') !== -1);
comp.toggleClass('first-class second-class');
assert.ok(comp.el().className.indexOf('first-class second-class') === -1);

comp.dispose();
});

QUnit.test('should add CSS class passed in options', function(assert) {
const comp = new Component(this.player, {className: 'class1 class2'});

Expand Down
42 changes: 28 additions & 14 deletions test/unit/utils/dom.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,44 +65,56 @@ QUnit.test('should insert an element first in another', function(assert) {
QUnit.test('addClass()', function(assert) {
const el = document.createElement('div');

assert.expect(5);
assert.expect(6);

Dom.addClass(el, 'test-class');
assert.strictEqual(el.className, 'test-class', 'adds a single class');

Dom.addClass(el, 'test-class');
assert.strictEqual(el.className, 'test-class', 'does not duplicate classes');

assert.throws(function() {
Dom.addClass(el, 'foo foo-bar');
}, 'throws when attempting to add a class with whitespace');

Dom.addClass(el, 'test2_className');
assert.strictEqual(el.className, 'test-class test2_className', 'adds second class');

Dom.addClass(el, 'FOO');
assert.strictEqual(el.className, 'test-class test2_className FOO', 'adds third class');

Dom.addClass(el, 'left-class', 'right-class');
assert.strictEqual(el.className, 'test-class test2_className FOO left-class right-class', 'adds two classes');

Dom.addClass(el, 'l-class r-class');
assert.strictEqual(
el.className,
'test-class test2_className FOO left-class right-class l-class r-class',
'adds two classes via one string'
);
});

QUnit.test('removeClass()', function(assert) {
const el = document.createElement('div');

el.className = 'test-class test2_className FOO bar';

assert.expect(4);
assert.expect(5);

Dom.removeClass(el, 'test-class');
assert.strictEqual(el.className, 'test2_className FOO bar', 'removes one class');

assert.throws(function() {
Dom.removeClass(el, 'test2_className bar');
}, 'throws when attempting to remove a class with whitespace');

Dom.removeClass(el, 'test2_className');
assert.strictEqual(el.className, 'FOO bar', 'removes another class');

Dom.removeClass(el, 'FOO');
assert.strictEqual(el.className, 'bar', 'removes another class');

el.className = 'bar left-class right-class';

Dom.removeClass(el, 'left-class', 'right-class');
assert.strictEqual(el.className, 'bar', 'removes two classes');

el.className = 'bar l-class r-class';

Dom.removeClass(el, 'l-class r-class');
assert.strictEqual(el.className, 'bar', 'removes two classes via one string');
});

QUnit.test('hasClass()', function(assert) {
Expand Down Expand Up @@ -217,17 +229,19 @@ QUnit.test('toggleClass()', function(assert) {
}
];

assert.expect(3 + predicateToggles.length);
assert.expect(4 + predicateToggles.length);

Dom.toggleClass(el, 'bar');
assert.strictEqual(el.className, 'foo', 'toggles a class off, if present');

Dom.toggleClass(el, 'bar');
assert.strictEqual(el.className, 'foo bar', 'toggles a class on, if absent');

assert.throws(function() {
Dom.toggleClass(el, 'foo bar');
}, 'throws when attempting to toggle a class with whitespace');
Dom.toggleClass(el, 'bla ok');
assert.strictEqual(el.className, 'foo bar bla ok', 'toggles a classes on, if absent');

Dom.toggleClass(el, 'bla ok');
assert.strictEqual(el.className, 'foo bar', 'toggles a classes off, if present');

predicateToggles.forEach(x => {
Dom.toggleClass(el, x.toggle, x.predicate);
Expand Down

0 comments on commit 9b3d941

Please sign in to comment.