How to write an replaceClass function

JavaScript polyfill, by , Saturday, March 29th, 2014

Maybe you have, like me, come across situations where you need to replace one or more classes on an element with some other classes, using JavaScript. For this kind of situation it is convenient with a method that does this directly instead of having to run both addClass and removeClass methods on the same object(s). I am going to write the replaceClass methods taking the following parameters;

  • obj the DOM element(s) being manipulated.
  • tagcl the target class(es), meaning the class(es) wished replaced. Can be either a string or array.
  • repcl the replacement class(es), meaning the classes to replace the tagcl class(es) with. Can be either a string or array.
  • rem a Boolean. This is meant to be used to clean up classes. If you have an element that already have both the current tagcl class and the respective repcl class, then if rem is set to true the target class will be removed while the replacement class will be kept. If on the other hand you leave it to be false (which is default) both classes will be kept.

replaceClass method using nested for-loop

The first version of our replaceClass function is going to be written using two for-loops, one nested inside the other. The outer loop is going to iterate over the DOM elements passed, and the inner loop is going to loop over the tagcl classes, so lets have a look;

  1. var replaceClass = function (obj, tagcl, repcl, rem) {
  2. "use strict";
  3. var i,j,
  4. o = isObjClass(obj, 'NodeList') ? obj : [obj],
  5. oLen = o.length,
  6. tagcl = isObjClass(tagcl, 'Array') ? tagcl : tagcl.split(' '),
  7. clLen = tagcl.length,
  8. repcl = isObjClass(repcl, 'Array') ? repcl : repcl.split(' '),
  9. oClass,
  10. tagIndex,
  11. repIndex;
  12. for (i = 0; i < oLen; i += 1) {
  13. oClass = o[i].className.split(' ');
  14. for (j = 0; j < clLen; j += 1) {
  15. tagIndex = oClass.indexOf(tagcl[j]);
  16. repIndex = oClass.indexOf(repcl[j]);
  17. if (tagIndex >= 0) {
  18. if (repIndex < 0) {
  19. oClass[tagIndex] = repcl[j];
  20. } else if (rem) {
  21. oClass.splice(tagIndex, 1);
  22. }
  23. }
  24. }
  25. o[i].className = oClass.join(' ');
  26. }
  27. };

The way this works is, that we first in line 4, 6, and 8 check if the input is in the right format, and if not make sure they become so. This method assumes, that you already have the isObjClass method available in some outer scope, as we need it to make reliable checks on our input parameters. You can find the method, and an explanation of how it works in my article How to get the class of an object value. From line 13 we iterate over the DOM elements passed, starting with at line 14, making an array out of the current elements class names. We then at line 15 iterate over the target classes (tagcl) saving the current target class and corresponding replacement class index numbers (line 16 - 17) in the object class array (oClass) for future reference. At line 18 we make a check to see if the target class is present on the current object (tagIndex greater or equal to 0 means it is present, -1 means it is not) and if not, nothing is done. If on the other hand it is present, we check that the replacement class (repcl) is not present at line 19. If it is not, we can replace the target class with the replacement class at line 20. If it turns out, that the replacement class already is present on the element, we make at check to see, if the rem parameter is set to true. If it is we remove the target class, (line 22) such that only the replacement class left. When we have iterated over all the replacement classes, we join the array of the elements classes and store it back in to the elements className property. I hope this run through was easy enough to follow, but if not, then it really helps trying out the method yourself, experimenting with different setups. If you do so, remember the isObjClass method.

Writing a replaceClass method using forEach and recursion

Just for the fun of it, I have come up with yet another version of the replaceClass function, this time using recursion and the build in Array.prototype.forEach method. If you want your code to work in older version of IE, then you might need a polyfill for the forEach method, and in that case you can use one of the version I have written in my article Array.prototype.forEach. Also worth noticing is, that if you read the documentation for the forEach method on Mozillas documentation page, you will find information about which parameters to expect in the callback function. You can also find this information in my polyfill article for the method. With that said, lets look at the code;

  1. var replaceClass = function (obj, tagcl, repcl, rem) {
  2. "use strict";
  3. var o = isObjClass(obj, 'NodeList') ? obj : [obj],
  4. tagcl = isObjClass(tagcl, 'Array') ? tagcl : tagcl.split(' '),
  5. repcl = isObjClass(repcl, 'Array') ? repcl : repcl.split(' ');
  6. (elmVal) {
  7. elmVal.className = (function repCls(oClass, i) {
  8. return tagcl[i] ? repCls((function () {
  9. var tagIndex = oClass.indexOf(tagcl[i]),
  10. repIndex = oClass.indexOf(repcl[i]);
  11. if (tagIndex >= 0) {
  12. if (repIndex < 0) {
  13. oClass[tagIndex] = repcl[i];
  14. } else if (rem) {
  15. oClass.splice(tagIndex, 1);
  16. }
  17. }
  18. return oClass;
  19. }()), i + 1) : oClass.join(' ');
  20. }(elmVal.className.split(' '), 0));
  21. });
  22. };

As you can see, this version is more compact, which is what you should expect when you replace your own code with some build in features, but lets have a quick look at what goes on. The interesting part starts at line 7 where we start out by turning the element collection into an array by calling the Array.prototype.slice method on o with o as the this value. This is the same technique we use, when we want to turn an arguments list into an array. At line 8 we assign the result of calling the repCls inline function to the current elements className property. The repCls the recursive function in our code, calling it self as long as we have a valid target class to check for. So at line 9 we check if we have such a valid target class in the tagcl array, and if we do we return the result of calling the repCls function, and if we do not have a valid class, we return the element classes concatenated back into a string. As you can see on line 8, the repCls function takes two parameters, the first one being the array of classes currently present on the element being checked. The second parameter is the iterator needed to get the current class in the arrays. So when we call the repCls on line 9, the first parameter is the result of executing an anonymous IIFE. The reason we do this is, that this is the class array we might need to manipulate. Inside this function the same things happen as from line 16 - 23 in the previous example, i.e. we do some checks to determine if we need to replace a class or not, or if we need to remove a class or not. The only difference between this version and the previous version, in respect to this part of the code, is that at line 19 in this example, we return the oClass array, as it is to be passed as the first parameter to the repCls function. This whole process then repeats itself again for the next element traversed by the forEach method.

If you find any of this useful, feel free to use the code as you please, though I would appreciate if you quote where you got it from, should you use it in one of your own articles.