Skip to content

Commit

Permalink
CSS: Skip falsy values in addClass( array ), compress code
Browse files Browse the repository at this point in the history
This change makes jQuery skip falsy values in `addClass( array )`
& `removeClass( array )` instead of stopping iteration when the first falsy
value is detected. This makes code like:
```js
elem.addClass( [ "a", "", "b" ] );
```
add both the `a` & `b` classes.

The code was also optimized for size a bit so it doesn't increase the
minified gzipped size.

Fixes gh-4998
Closes gh-5003

(partially cherry picked from commit a338b40)
  • Loading branch information
mgol committed Jan 24, 2022
1 parent 95e34b6 commit 9b34bdb
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 36 deletions.
70 changes: 34 additions & 36 deletions src/attributes/classes.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,45 +25,43 @@ function classesToArray( value ) {

jQuery.fn.extend( {
addClass: function( value ) {
var classes, elem, cur, curValue, clazz, j, finalValue,
i = 0;
var classNames, cur, curValue, className, i, finalValue;

if ( isFunction( value ) ) {
return this.each( function( j ) {
jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
} );
}

classes = classesToArray( value );
classNames = classesToArray( value );

if ( classes.length ) {
while ( ( elem = this[ i++ ] ) ) {
curValue = getClass( elem );
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
if ( classNames.length ) {
return this.each( function() {
curValue = getClass( this );
cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );

if ( cur ) {
j = 0;
while ( ( clazz = classes[ j++ ] ) ) {
if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
cur += clazz + " ";
for ( i = 0; i < classNames.length; i++ ) {
className = classNames[ i ];
if ( cur.indexOf( " " + className + " " ) < 0 ) {
cur += className + " ";
}
}

// Only assign if different to avoid unneeded rendering.
finalValue = stripAndCollapse( cur );
if ( curValue !== finalValue ) {
elem.setAttribute( "class", finalValue );
this.setAttribute( "class", finalValue );
}
}
}
} );
}

return this;
},

removeClass: function( value ) {
var classes, elem, cur, curValue, clazz, j, finalValue,
i = 0;
var classNames, cur, curValue, className, i, finalValue;

if ( isFunction( value ) ) {
return this.each( function( j ) {
Expand All @@ -75,45 +73,42 @@ jQuery.fn.extend( {
return this.attr( "class", "" );
}

classes = classesToArray( value );
classNames = classesToArray( value );

if ( classes.length ) {
while ( ( elem = this[ i++ ] ) ) {
curValue = getClass( elem );
if ( classNames.length ) {
return this.each( function() {
curValue = getClass( this );

// This expression is here for better compressibility (see addClass)
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );

if ( cur ) {
j = 0;
while ( ( clazz = classes[ j++ ] ) ) {
for ( i = 0; i < classNames.length; i++ ) {
className = classNames[ i ];

// Remove *all* instances
while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
cur = cur.replace( " " + clazz + " ", " " );
while ( cur.indexOf( " " + className + " " ) > -1 ) {
cur = cur.replace( " " + className + " ", " " );
}
}

// Only assign if different to avoid unneeded rendering.
finalValue = stripAndCollapse( cur );
if ( curValue !== finalValue ) {
elem.setAttribute( "class", finalValue );
this.setAttribute( "class", finalValue );
}
}
}
} );
}

return this;
},

toggleClass: function( value, stateVal ) {
var type = typeof value,
var classNames, className, i, self,
type = typeof value,
isValidValue = type === "string" || Array.isArray( value );

if ( typeof stateVal === "boolean" && isValidValue ) {
return stateVal ? this.addClass( value ) : this.removeClass( value );
}

if ( isFunction( value ) ) {
return this.each( function( i ) {
jQuery( this ).toggleClass(
Expand All @@ -123,17 +118,20 @@ jQuery.fn.extend( {
} );
}

return this.each( function() {
var className, i, self, classNames;
if ( typeof stateVal === "boolean" && isValidValue ) {
return stateVal ? this.addClass( value ) : this.removeClass( value );
}

classNames = classesToArray( value );

return this.each( function() {
if ( isValidValue ) {

// Toggle individual class names
i = 0;
self = jQuery( this );
classNames = classesToArray( value );

while ( ( className = classNames[ i++ ] ) ) {
for ( i = 0; i < classNames.length; i++ ) {
className = classNames[ i ];

// Check each className given, space separated list
if ( self.hasClass( className ) ) {
Expand Down
38 changes: 38 additions & 0 deletions test/unit/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -1631,6 +1631,44 @@ QUnit.test( "addClass, removeClass, hasClass on elements with classes with non-H
testMatches();
} );

( function() {
var rnothtmlwhite = /[^\x20\t\r\n\f]+/g;

function expectClasses( assert, elem, classes ) {
var actualClassesSorted = ( elem.attr( "class" ).match( rnothtmlwhite ) || [] )
.sort().join( " " );
var classesSorted = classes.slice()
.sort().join( " " );
assert.equal( actualClassesSorted, classesSorted, "Expected classes present" );
}

QUnit.test( "addClass on arrays with falsy elements (gh-4998)", function( assert ) {
assert.expect( 3 );

var elem = jQuery( "<div class='a'></div>" );

elem.addClass( [ "b", "", "c" ] );
expectClasses( assert, elem, [ "a", "b", "c" ] );
elem.addClass( [ "", "d" ] );
expectClasses( assert, elem, [ "a", "b", "c", "d" ] );
elem.addClass( [ "e", "" ] );
expectClasses( assert, elem, [ "a", "b", "c", "d", "e" ] );
} );

QUnit.test( "removeClass on arrays with falsy elements (gh-4998)", function( assert ) {
assert.expect( 3 );

var elem = jQuery( "<div class='a b c d e'></div>" );

elem.removeClass( [ "e", "" ] );
expectClasses( assert, elem, [ "a", "b", "c", "d" ] );
elem.removeClass( [ "", "d" ] );
expectClasses( assert, elem, [ "a", "b", "c" ] );
elem.removeClass( [ "b", "", "c" ] );
expectClasses( assert, elem, [ "a" ] );
} );
} )();

QUnit.test( "contents().hasClass() returns correct values", function( assert ) {
assert.expect( 2 );

Expand Down

0 comments on commit 9b34bdb

Please sign in to comment.