86

I'm using the following code, but having it fail in IE. The message is:

Unable to get value of the property 'add': object is null or undefined"

I assume this is just an IE support issue. How would you make the following code work in IE?

Any ideas?

var img = new Image();
img.src = '/image/file.png';
img.title = 'this is a title';
img.classList.add("profilePic");
var div = document.createElement("div");
div.classList.add("picWindow");
div.appendChild(img);
content.appendChild(div);
Wesley
  • 5,381
  • 9
  • 42
  • 65
  • 5
    IE doesn't have classList, hence it's null or undefined – Esailija Nov 11 '11 at 18:36
  • 2
    Do you want to use jQuery or not? – arb Nov 11 '11 at 18:37
  • 1
    @Zero21xxx Don't mind it, but that would require me to rewrite all the above code to use JQuery objects instead of standard JS. – Wesley Nov 11 '11 at 18:46
  • @Wesley: Really? Rewriting all the above code with jQuery would take about 30 seconds, and Zero21xxx did it for you. You also don't have to rewrite it all, just the part that is adding to the `classList`. A better argument would be: I don't want to add jQuery just for manipulating the list of classes – Ruan Mendes Nov 11 '11 at 20:43
  • 3
    I wrote some [polyfills here for `classList`](http://blog.alexanderdickson.com/manipulating-classes-with-javascript). – alex Feb 27 '13 at 11:35
  • 1
    @alex Your blog post could do with an update. First, the final link (MDN) is wrong, `querySelector` should be `classList`. Second, the `.remove` method contains an unnecessary RegExp, whose use -as you've acknowledged- introduces a bug. Because you've already pre- and suffixed spaces, a simple `.replace(' ' + className + ' ', ' ')` is sufficient (furthermore, the remark *"A valid className shouldn’t contain any special regex characters."* is incorrect, see [spec](http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#classes) (also true for HTML4)) – Rob W Feb 27 '13 at 14:00
  • @RobW All good points and they've now been addressed :) – alex Feb 27 '13 at 20:31
  • Try a classList shim – Leo Dec 04 '14 at 00:32
  • What if you want to remove and not add()? – Fandango68 Sep 29 '15 at 01:23

7 Answers7

95

The classList property is not supported by IE9 and lower. IE10+ supports it though.
Use className += " .." instead. Note: Do not omit the space: class names should be added in a white-space separated list.

var img = new Image();
img.src = '/image/file.png';
img.title = 'this is a title';
img.className += " profilePic"; // Add profilePic class to the image

var div = document.createElement("div");
div.className += " picWindow";  // Add picWindow class to the div
div.appendChild(img);
content.appendChild(div);
Rob W
  • 341,306
  • 83
  • 791
  • 678
26

As mentioned by othes, classList is not supported by IE9 and older. As well as Alex's alternative above, there are a couple of polyfills which aim to be a drop-in replacement, i.e. just include these in your page and IE should just work (famous last words!).

https://github.com/eligrey/classList.js/blob/master/classList.js

https://gist.github.com/devongovett/1381839

tagawa
  • 4,561
  • 2
  • 27
  • 34
  • 11
    I am using IE 11 and classList does not seem to be working (works in chrome) – john k Nov 13 '14 at 23:58
  • 1
    That's strange - caniuse.com says it should be supported from IE10+. I suggest asking @TheWebJustWorks on Twitter or filing an issue at http://webcompat.com – tagawa Nov 25 '14 at 09:04
  • 3
    @johnktejik if you add or remove multiple classes at once, it won't work in IE11, Firefox 24- and Chrome 24-. Was that what you were doing? – mnsth Jan 23 '15 at 22:59
  • @mnsth I presumed that was what was giving me issues - thanks for confirming. Is there reference to this _bug_ anywhere? – Brad Adams Jan 18 '17 at 00:44
  • 1
    @BradAdams [Here](https://connect.microsoft.com/IE/Feedback/Details/920755/) and [here](https://connect.microsoft.com/IE/feedback/details/878564/element-classlist-toggle-does-not-support-second-parameter). Caniuse and MDN also have reports. All those issues are fixed in Microsoft Edge, though! – mnsth Feb 07 '17 at 07:28
  • 3
    classList is still not supported on SVG elements, these polyfills do the trick – Shoplifter.Doe Nov 07 '17 at 11:07
11

In IE 10 & 11, The classList property is defined on HTMLElement.prototype.

In order to get it to work on SVG-elements, the property should be defined on Element.prototype, like it has been on other browsers.

A very small fix would be copying the exact propertyDescriptor from HTMLElement.prototype to Element.prototype:

if (!Object.getOwnPropertyDescriptor(Element.prototype,'classList')){
    if (HTMLElement&&Object.getOwnPropertyDescriptor(HTMLElement.prototype,'classList')){
        Object.defineProperty(Element.prototype,'classList',Object.getOwnPropertyDescriptor(HTMLElement.prototype,'classList'));
    }
}
  • We need to copy the descriptor, since Element.prototype.classList = HTMLElement.prototype.classList will throw Invalid calling object
  • The first check prevents overwriting the property on browsers which support is natively.
  • The second check is prevents errors on IE versions prior to 9, where HTMLElement is not implemented yet, and on IE9 where classList is not implemented.

For IE 8 & 9, use the following code, I also included a (minified) polyfill for Array.prototype.indexOf, because IE 8 does not support it natively (polyfill source: Array.prototype.indexOf

//Polyfill Array.prototype.indexOf
Array.prototype.indexOf||(Array.prototype.indexOf=function (value,startIndex){'use strict';if (this==null)throw new TypeError('array.prototype.indexOf called on null or undefined');var o=Object(this),l=o.length>>>0;if(l===0)return -1;var n=startIndex|0,k=Math.max(n>=0?n:l-Math.abs(n),0)-1;function sameOrNaN(a,b){return a===b||(typeof a==='number'&&typeof b==='number'&&isNaN(a)&&isNaN(b))}while(++k<l)if(k in o&&sameOrNaN(o[k],value))return k;return -1});

// adds classList support (as Array) to Element.prototype for IE8-9
Object.defineProperty(Element.prototype,'classList',{
    get:function(){
        var element=this,domTokenList=(element.getAttribute('class')||'').replace(/^\s+|\s$/g,'').split(/\s+/g);
        if (domTokenList[0]==='') domTokenList.splice(0,1);
        function setClass(){
            if (domTokenList.length > 0) element.setAttribute('class', domTokenList.join(' ');
            else element.removeAttribute('class');
        }
        domTokenList.toggle=function(className,force){
            if (force!==undefined){
                if (force) domTokenList.add(className);
                else domTokenList.remove(className);
            }
            else {
                if (domTokenList.indexOf(className)!==-1) domTokenList.splice(domTokenList.indexOf(className),1);
                else domTokenList.push(className);
            }
            setClass();
        };
        domTokenList.add=function(){
            var args=[].slice.call(arguments)
            for (var i=0,l=args.length;i<l;i++){
                if (domTokenList.indexOf(args[i])===-1) domTokenList.push(args[i])
            };
            setClass();
        };
        domTokenList.remove=function(){
            var args=[].slice.call(arguments)
            for (var i=0,l=args.length;i<l;i++){
                if (domTokenList.indexOf(args[i])!==-1) domTokenList.splice(domTokenList.indexOf(args[i]),1);
            };
            setClass();
        };
        domTokenList.item=function(i){
            return domTokenList[i];
        };
        domTokenList.contains=function(className){
            return domTokenList.indexOf(className)!==-1;
        };
        domTokenList.replace=function(oldClass,newClass){
            if (domTokenList.indexOf(oldClass)!==-1) domTokenList.splice(domTokenList.indexOf(oldClass),1,newClass);
            setClass();
        };
        domTokenList.value = (element.getAttribute('class')||'');
        return domTokenList;
    }
});
Kevin Drost
  • 383
  • 4
  • 7
10
    /**
 * Method that checks whether cls is present in element object.
 * @param  {Object} ele DOM element which needs to be checked
 * @param  {Object} cls Classname is tested
 * @return {Boolean} True if cls is present, false otherwise.
 */
function hasClass(ele, cls) {
    return ele.getAttribute('class').indexOf(cls) > -1;
}

/**
 * Method that adds a class to given element.
 * @param  {Object} ele DOM element where class needs to be added
 * @param  {Object} cls Classname which is to be added
 * @return {null} nothing is returned.
 */
function addClass(ele, cls) {
    if (ele.classList) {
        ele.classList.add(cls);
    } else if (!hasClass(ele, cls)) {
        ele.setAttribute('class', ele.getAttribute('class') + ' ' + cls);
    }
}

/**
 * Method that does a check to ensure that class is removed from element.
 * @param  {Object} ele DOM element where class needs to be removed
 * @param  {Object} cls Classname which is to be removed
 * @return {null} Null nothing is returned.
 */
function removeClass(ele, cls) {
    if (ele.classList) {
        ele.classList.remove(cls);
    } else if (hasClass(ele, cls)) {
        ele.setAttribute('class', ele.getAttribute('class').replace(cls, ' '));
    }
}
Ritesh Kumar
  • 101
  • 1
  • 3
  • 2
    +1, however: I had `ele.getAttribute('class')` return null in some cases (maybe if the class attribute is not set yet?) - a simple if statement solved it. – Jeppe Jul 17 '18 at 11:32
8

check this out

Object.defineProperty(Element.prototype, 'classList', {
    get: function() {
        var self = this, bValue = self.className.split(" ")

        bValue.add = function (){
            var b;
            for(i in arguments){
                b = true;
                for (var j = 0; j<bValue.length;j++)
                    if (bValue[j] == arguments[i]){
                        b = false
                        break
                    }
                if(b)
                    self.className += (self.className?" ":"")+arguments[i]
            }
        }
        bValue.remove = function(){
            self.className = ""
            for(i in arguments)
                for (var j = 0; j<bValue.length;j++)
                    if(bValue[j] != arguments[i])
                        self.className += (self.className?" " :"")+bValue[j]
        }
        bValue.toggle = function(x){
            var b;
            if(x){
                self.className = ""
                b = false;
                for (var j = 0; j<bValue.length;j++)
                    if(bValue[j] != x){
                        self.className += (self.className?" " :"")+bValue[j]
                        b = false
                    } else b = true
                if(!b)
                    self.className += (self.className?" ":"")+x
            } else throw new TypeError("Failed to execute 'toggle': 1 argument required")
            return !b;
        }

        return bValue; 
    },
    enumerable: false
})

and classList will work!

document.getElementsByTagName("div")[0].classList
["aclass"]

document.getElementsByTagName("div")[0].classList.add("myclass")

document.getElementsByTagName("div")[0].className
"aclass myclass"

that's all!

asdru
  • 1,147
  • 10
  • 19
  • I am a noob. Where do I place the above? In the – Fandango68 Sep 29 '15 at 01:21
  • I inserted this code into a script block immediately above the offending code - works very nice. Thanks. – robnick Jan 06 '16 at 03:17
  • 1
    Good work, unfortunately this does not work for me in all cases, I did find this one however: https://github.com/remy/polyfills/blob/6e87470526c496c0fc53fa87ed5a825eff61f1f3/classList.js – Michiel Apr 25 '16 at 06:46
5

In Explorer 11 classList.add works with single values only.

Element.classList.add("classOne", "classTwo");

In this case Explorer adds only first class and ignores the second one. So need to do:

Element.classList.add("classOne");
Element.classList.add("classTwo");
Alonad
  • 1,986
  • 19
  • 17
0

classList is not supported in IE < 9. Use jQuery.addClass or a polyfill like the one at https://developer.mozilla.org/en-US/docs/Web/API/Element/classList

Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
  • 1
    To whoever downvoted this: this was true when the answer was posted, and only IE 9 started supporting it – Ruan Mendes Jul 08 '13 at 21:19
  • 2
    I'd hazard a guess it was downvoted because the question didn't ask for jQuery. There are ways around problems that don't involve using an entire Javascript library. – Vincent McNabb Apr 14 '14 at 22:47
  • 4
    -1: yeah .. right ... lets add 100KB library to badly mimic `classList` functionality – tereško Jun 07 '14 at 13:52
  • 1
    @tereško It does say "or something like it", in case you're not already using jQuery. Note that the accepted answer could add the same class name twice since it's not checking if it already exists. – Ruan Mendes Nov 14 '14 at 16:45