Javascript Icon Get 62% off the JavaScript Master bundle

See the bundle then add to cart and your discount is applied.

0 days
00 hours
00 mins
00 secs

Write JavaScript like a pro. Javascript Icon

Follow the ultimate JavaScript roadmap.

Apollo.js, standalone class manipulation API for HTML5 and legacy DOM

Apollo is a <1KB standalone DOM class manipulation API for adding, removing, toggling and testing the existence of classes on an element. Apollo is the successor to an original post I published on raw JavaScript class functions earlier this year, but is completely rewritten and enhanced for the next level, whilst integrating HTML5.

Under the hood, Apollo uses the HTML5 classList API (jQuery isn’t even using this yet!) when available and fallbacks to manual class manipulation for legacy support, making it the most powerful class manipulation API on the web. HTML5 classList performance far outweighs the legacy method.

Support? IE6+ for legacy support and internal feature detection to switch to HTML5 when available. Cross-browser compatible.

I’ll talk you through the APIs for Apollo.

addClass API

To add a class using Apollo, use the addClass API, which takes an element and a single class name.

Apollo.addClass(element, className);

removeClass API

To remove a class using Apollo, use the removeClass API, which takes an element and a single class name.

Apollo.removeClass(element, className);

toggleClass API

To toggle a class using Apollo, use the toggleClass API, which takes an element and a single class name.

Apollo.toggleClass(element, className);

hasClass API

To test the existence of a class using Apollo, use the hasClass API, which takes an element and a single class name. The hasClass API returns a boolean (true/false) with the result.

Apollo.hasClass(element, className);

Improvements from inception

When I first wrote the APIs to allow you to create your own class manipulation functions, I used some while loops, and the implementation was good, not great. I’m going to look at the removeClass function now, and show you the difference in the new API.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

Old API:

The old API was complex, but worked fantastically. It’s important to note than when using a library that handles classes, that it actually removes all instances and doesn’t assume the class exists just once.

function hasClass(elem, className) {
  return new RegExp(' ' + className + ' ').test(' ' + elem.className + ' ');
}
function addClass(elem, className) {
    if (!hasClass(elem, className)) {
      elem.className += ' ' + className;
    }
}
function removeClass (elem, className) 
  var newClass = ' ' + elem.className.replace( /[\t\r\n]/g, ' ') + ' ';
  if (hasClass(elem, className)) {
    while (newClass.indexOf(' ' + className + ' ') >= 0 ) {
      newClass = newClass.replace(' ' + className + ' ', ' ');
    }
    elem.className = newClass.replace(/^\s+|\s+$/g, '');
  }
}
function toggleClass(elem, className) {
  var newClass = ' ' + elem.className.replace( /[\t\r\n]/g, " " ) + ' ';
    if (hasClass(elem, className)) {
        while (newClass.indexOf(" " + className + " ") >= 0 ) {
            newClass = newClass.replace( " " + className + " " , " " );
        }
        elem.className = newClass.replace(/^\s+|\s+$/g, '');
    } else {
        elem.className += ' ' + className;
    }
}

New API

The removeClass new API is part of an Object, so it’s not declared as a function like the above. As you can see, this is much cleaner, and uses one line for each removal technique too. It detects whether classList is available and rolls with that if so, or falls back to a RegExp replace on the string. The RegExp uses a ‘g’ declaration in the RegExp constructor, meaning global - and will do a global replace on the classname, removing it each time it’s present. I don’t know about you, but that’s a big improvement over file size and performance than the previous while looping.

hasClass: function (elem, className) {
  if (classList) {
    return elem.classList.contains(className);
  } else {
    return new RegExp('(^|\\s)' + className + '(\\s|$)').test(elem.className);
  }
},
addClass: function (elem, className) {
  if (!this.hasClass(elem, className)) {
    if (classList) {
      elem.classList.add(className);
    } else {
      elem.className += (elem.className ? ' ' : '') + className;
    }
  }
},
removeClass: function (elem, className) {
  if (this.hasClass(elem, className)) {
    if (classList) {
      elem.classList.remove(className);
    } else {
      elem.className = elem.className.replace(new RegExp('(^|\\s)*' + className + '(\\s|$)*', 'g'), '');
    }
  }
},
toggleClass: function (elem, className) {
  if (classList) {
    elem.classList.toggle(className);
  } else {
    if (this.hasClass(elem, className)) {
      elem.removeClass(className);
    } else {
      elem.addClass(className);
    }
  }
}

It’s also good to note that I’ve also added the entire classList Object and native manipulation checks, and it’s still smaller than the original :)

Why not Prototype?

I originally rewrote the API to fall into a Prototype pattern, which looked like this (and you can use instead if you really want):

Element.prototype.hasClass = function (className) {
    if (document.documentElement.classList) {
        return this.classList.contains(className);
    } else {
        return new RegExp('(^|\\s)' + className + '(\\s|$)').test(this.className);
    }
};
Element.prototype.addClass = function (className) {
    if (!this.hasClass(className)) {
        if (document.documentElement.classList) {
            this.classList.add(className);
        } else {
           this.className += (this.className ? ' ' : '') + className;
        }
    }
};
Element.prototype.removeClass = function (className) {
    if (this.hasClass(className)) {
        if (document.documentElement.classList) {
            this.classList.remove(className);
        } else {
            this.className = this.className.replace(new RegExp('(^|\\s)*' + className + '(\\s|$)*', 'g'), '');
        }
    }
};
Element.prototype.toggleClass = function (className) {
    if (document.documentElement.classList) {
        this.classList.toggle(className);
    } else {
        if (this.hasClass(className)) {
            this.removeClass(className);
        } else {
            this.addClass(className);
        }
    }
};

I’d advise against doing this though. If you’re including other libraries you might run into a lot of conflict when extending native DOM methods. It’s also considered bad practices by some to extend existing DOM by prototyping, which is exactly why I created the Apollo API.

The Apollo API is also part of a JavaScript Module and returned as an Object with several APIs. It gives you the benefit of proper abstraction, testing and speed - throwing a bunch of Prototype extensions into the DOM doesn’t.

Learn JavaScript the right way.

The most complete guide to learning JavaScript ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover