A new removeClasses utility for MooTools

Note: If you want to skip to the final code (in both MooTools and framework-neutral flavors), it’s at the bottom of this post.

The problem

Readers of this blog know that I enjoy MooTools. Like other JavaScript frameworks, it has many excellent features, including addClass and removeClass functions, which I use all the time. However, when I was working on my CustomInput class the other day, I discovered a major shortcoming of MooTools’ removeClass function — it doesn’t work very well when trying to remove multiple classes (as of MooTools version 1.2.4). In particular, if you specify multiple classes to remove, removeClass will only work if the classes are listed in that element’s className property in the order specified.


//Assuming element has className "hello cruel world"

//Single terms work fine
element.removeClass("world"); //becomes "hello  world"
element.removeClass("hello"); //becomes " cruel world"

//Multiple terms listed in same order as className works fine
element.removeClass("hello cruel"); //becomes " world"

//Multiple terms NOT listed in same order as className fail
element.removeClass("hello world"); //remains "hello cruel world"
element.removeClass("cruel hello"); //remains "hello cruel world"

The cause

A peek at MooTools’ removeClass code reveals the shortcoming:


removeClass: function(className){
   this.className = this.className.replace(new RegExp('(^|\s)' + className + '(?:\s|$)'), '$1');
   return this;
}

The string containing the class names you want to remove is passed as-is; it remains a whole string, and is not broken down into substrings. So "hello world" remains the single string "hello world" instead of two separate strings "hello" and "world".

I tested jQuery’s removeClass function and noticed it doesn’t have the same problem; it will remove each word, no matter what order you specify. Taking a look under the hood reveals a completely different approach to removeClass:


removeClass: function( value ) {
   if ( jQuery.isFunction(value) ) {
      return this.each(function(i) {
         var self = jQuery(this);
         self.removeClass( value.call(this, i, self.attr("class")) );
      });
   }

   if ( (value && typeof value === "string") || value === undefined ) {
      var classNames = (value || "").split(rspace);

      for ( var i = 0, l = this.length; i < l; i++ ) {
         var elem = this[i];

         if ( elem.nodeType === 1 && elem.className ) {
            if ( value ) {
               var className = (" " + elem.className + " ").replace(rclass, " ");
               for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
                  className = className.replace(" " + classNames[c] + " ", " ");
               }
               elem.className = jQuery.trim( className );

            } else {
               elem.className = "";
            }
         }
      }
   }

   return this;
}

The big difference between jQuery and MooTools in this case is that jQuery converts the arguments to an array (split using spaces as a delimiter) then loops through the className property to search for each word in the array, whereas MooTools performs a simple full-string replace using regular expressions.

The search for a solution

My first reaction was to build a MooTools-flavored variation of jQuery’s removeClass code.


Element.implement({
   removeClasses: function (classNames) {
      if(this.className){
         var classNameString = this.className;
         classNames.split(/s+/).each(function (term){
            classNameString = classNameString.replace(term, " ");
         });
         this.className = classNameString.clean();
      }
      return this;
   }
});

It follows the same basic principles of the jQuery version, but uses MooTools’ Array.each and String.clean utility functions. It works well, but I wasn’t thrilled about using a loop. I thought maybe a regular expression would be better suited for the job.

I eventually came up with this:
(purposely verbose to explain what’s happening)


Element.implement({
   removeClasses: function (classNames) {
      if(classNames && this.className){
         //Replace all spaces in classNames with vertical beams
         var terms = classNames.replace(/s+/g, "|");
         //Create a regular expression using terms variable
         var reg = new RegExp('\b(' + terms + ')\b', 'g');
         //Use the new regular expression to replace all specified terms with a space
         var newClass = this.className.replace(reg, " ");
         //Use MooTools' 'clean' method to remove extraneous spaces
         newClass = newClass.clean();
         //Set element's classname to new cleaned list of classes
         this.className = newClass;
      }
      return this;
   }
});
Notes:

  • I’m no expert on JavaScript speed tests, so for all I know a loop might be quicker. However, a regular expression feels more elegant.
  • I named my utility removesClasses so it doesn’t overwrite MooTools’ built-in utility.

Final MooTools version

Here’s a more concise version:


Element.implement({
   removeClasses: function (classNames) {
      this.className = this.className.replace(new RegExp("\b(" + classNames.replace(/s+/g, "|") + ")\b", "g"), " ").clean();
      return this;
   }
});

You can see it in action via the MooShell (apparently the MooTools Shell is no longer online)

Standalone (framework-neutral) version

For those of you who don’t use MooTools, a few small edits will allow you to use this code without relying on any outside JavaScript libraries. First, a verbose version explaining what’s happening:


function removeClass(el, classNames) {
   //Only run if the element is available and supports the className property
   if(el && el.className && classNames){
         //Replace all spaces in classNames with vertical beams
         var terms = classNames.replace(/s+/g, "|");
         //Create a regular expression using terms variable
         var reg = new RegExp('\b(' + terms + ')\b', 'g');
         //Use the new regular expression to replace all specified terms with a space
         var newClass = el.className.replace(reg, " ");
         //Use regular expression to remove extraneous whitespace between class names
         newClass = newClass.replace(/s+/g, " ");
         //Use regular expression to remove all whitespace at front and end of string
         newClass = newClass.replace(/^s+|s+$/g, "");
         //Set element's classname to new cleaned list of classes
         el.className = newClass;
   }
}

The concise version:


function removeClass(el, classNames) {
   if(el && el.className && classNames){
      el.className = el.className.replace(new RegExp("\b(" + classNames.replace(/s+/g, "|") + ")\b", "g"), " ").replace(/s+/g, " ").replace(/^s+|s+$/g, "");
   }
}

Used as follows:


var myelement = document.getElementById("myelement");
removeClass(myelement, "one three");
//<div id="myelement" class="one two three"></div>
//becomes
//<div id="myelement" class="two"></div>

Enjoy!

Successfully tested in Internet Explorer 6 (WinXP), Firefox 3.5 (WinXP), Firefox 3.6 (OS X 10.6.2), Safari 4 (OS X 10.6.2), Opera 10.1 (OS X 10.6.2), Chrome 5 (OS X 10.6.2)

3 Replies to “A new removeClasses utility for MooTools”

  1. Nice addition, thanks for sharing. It also appears that the native 1.2.4 Element.removeClass() does not trim whitespace where multiple, space-delimited classes previously existed. Selectors like div[class=whatever] will fail when the class contains whitespace (class=”whatever”) because it’s not an exact match. It seems to me this should be fixed in core so I reimplemented removeClass() with a call to the .clean() method.

    Element.implement({	
    	removeClass: function(className){
    		this.className = this.className.replace(new RegExp('(^|\s)' + className + '(?:\s|$)'), '$1').clean();
    		return this;
    	}
    })
    

Leave a Reply

Your email address will not be published. Required fields are marked *