0

I have the following javascript function to open and close sub list elements on an onclick event:

function ShowHideDtls(itId) {
    var subMen = document.getElementById(itId);
    if (subMen != null) {
        if (subMen.className == "nav nav-second-level collapse in") {
            subMen.className = "nav nav-second-level collapse";
        } else {
            subMen.className += " in";
        }
    }
}

The "collapse" is a css class which makes display=none hiding the sub list and "in" is a class which makes display=block showing the sub list, creating a menu with submenus.

I found in this question Change an element's class with JavaScript in the first(accepted) answer use of a regex in order to do this. I tried it like this:

function ShowHideDtls(itId) {
    var subMen = document.getElementById(itId);
    if (subMen != null) {
        if (subMen.className.match(/(?:^|\s)in(?!\S)/)) {
            subMen.className.replace(/(?:^|\s)in(?!\S)/g, '');
        } else {
            subMen.className += " in";
        }
    }
}

The code without the regex works perfectly but with the regex it doesn't. I checked the regex in regex101.com and it seems to work there. As I understand it's more appropriate to use the regex than a long string of all the class names and also I also have a nav-third-level class that I have to close and open so the regex seems to be the convenient and proper way to do it. What's wrong? Thank you.

Community
  • 1
  • 1
Dov Miller
  • 1,958
  • 5
  • 34
  • 46

2 Answers2

3

No need of regex here. You can use classList

Using classList is a convenient alternative to accessing an element's list of classes as a space-delimited string via element.className.

function ShowHideDtls(itId) {
    var subMen = document.getElementById(itId);
    if (subMen != null) {
        subMen.classList.toggle('in');
    }
}

toggle() will toggle the class of the element. If the element already has the class, it'll remove it, if not then toggle will add the class to the element.

Check the Browser Compatibility.

You can use following SHIM from MDN for IE9,

/* 
 * classList.js: Cross-browser full element.classList implementation.
 * 2014-07-23
 *
 * By Eli Grey, http://eligrey.com
 * Public Domain.
 * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
 */

/*global self, document, DOMException */

/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/

if ("document" in self) {

    // Full polyfill for browsers with no classList support
    if (!("classList" in document.createElement("_"))) {

        (function (view) {

            "use strict";

            if (!('Element' in view)) return;

            var
                classListProp = "classList",
                protoProp = "prototype",
                elemCtrProto = view.Element[protoProp],
                objCtr = Object,
                strTrim = String[protoProp].trim || function () {
                    return this.replace(/^\s+|\s+$/g, "");
                },
                arrIndexOf = Array[protoProp].indexOf || function (item) {
                    var
                        i = 0,
                        len = this.length;
                    for (; i < len; i++) {
                        if (i in this && this[i] === item) {
                            return i;
                        }
                    }
                    return -1;
                }
                // Vendors: please allow content code to instantiate DOMExceptions
                ,
                DOMEx = function (type, message) {
                    this.name = type;
                    this.code = DOMException[type];
                    this.message = message;
                },
                checkTokenAndGetIndex = function (classList, token) {
                    if (token === "") {
                        throw new DOMEx(
                            "SYNTAX_ERR", "An invalid or illegal string was specified"
                        );
                    }
                    if (/\s/.test(token)) {
                        throw new DOMEx(
                            "INVALID_CHARACTER_ERR", "String contains an invalid character"
                        );
                    }
                    return arrIndexOf.call(classList, token);
                },
                ClassList = function (elem) {
                    var
                        trimmedClasses = strTrim.call(elem.getAttribute("class") || ""),
                        classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [],
                        i = 0,
                        len = classes.length;
                    for (; i < len; i++) {
                        this.push(classes[i]);
                    }
                    this._updateClassName = function () {
                        elem.setAttribute("class", this.toString());
                    };
                },
                classListProto = ClassList[protoProp] = [],
                classListGetter = function () {
                    return new ClassList(this);
                };
            // Most DOMException implementations don't allow calling DOMException's toString()
            // on non-DOMExceptions. Error's toString() is sufficient here.
            DOMEx[protoProp] = Error[protoProp];
            classListProto.item = function (i) {
                return this[i] || null;
            };
            classListProto.contains = function (token) {
                token += "";
                return checkTokenAndGetIndex(this, token) !== -1;
            };
            classListProto.add = function () {
                var
                    tokens = arguments,
                    i = 0,
                    l = tokens.length,
                    token, updated = false;
                do {
                    token = tokens[i] + "";
                    if (checkTokenAndGetIndex(this, token) === -1) {
                        this.push(token);
                        updated = true;
                    }
                }
                while (++i < l);

                if (updated) {
                    this._updateClassName();
                }
            };
            classListProto.remove = function () {
                var
                    tokens = arguments,
                    i = 0,
                    l = tokens.length,
                    token, updated = false,
                    index;
                do {
                    token = tokens[i] + "";
                    index = checkTokenAndGetIndex(this, token);
                    while (index !== -1) {
                        this.splice(index, 1);
                        updated = true;
                        index = checkTokenAndGetIndex(this, token);
                    }
                }
                while (++i < l);

                if (updated) {
                    this._updateClassName();
                }
            };
            classListProto.toggle = function (token, force) {
                token += "";

                var
                    result = this.contains(token),
                    method = result ?
                    force !== true && "remove" :
                    force !== false && "add";

                if (method) {
                    this[method](token);
                }

                if (force === true || force === false) {
                    return force;
                } else {
                    return !result;
                }
            };
            classListProto.toString = function () {
                return this.join(" ");
            };

            if (objCtr.defineProperty) {
                var classListPropDesc = {
                    get: classListGetter,
                    enumerable: true,
                    configurable: true
                };
                try {
                    objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
                } catch (ex) { // IE 8 doesn't support enumerable:true
                    if (ex.number === -0x7FF5EC54) {
                        classListPropDesc.enumerable = false;
                        objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
                    }
                }
            } else if (objCtr[protoProp].__defineGetter__) {
                elemCtrProto.__defineGetter__(classListProp, classListGetter);
            }

        }(self));

    } else {
        // There is full or partial native classList support, so just check if we need
        // to normalize the add/remove and toggle APIs.

        (function () {
            "use strict";

            var testElement = document.createElement("_");

            testElement.classList.add("c1", "c2");

            // Polyfill for IE 10/11 and Firefox <26, where classList.add and
            // classList.remove exist but support only one argument at a time.
            if (!testElement.classList.contains("c2")) {
                var createMethod = function (method) {
                    var original = DOMTokenList.prototype[method];

                    DOMTokenList.prototype[method] = function (token) {
                        var i, len = arguments.length;

                        for (i = 0; i < len; i++) {
                            token = arguments[i];
                            original.call(this, token);
                        }
                    };
                };
                createMethod('add');
                createMethod('remove');
            }

            testElement.classList.toggle("c3", false);

            // Polyfill for IE 10 and Firefox <24, where classList.toggle does not
            // support the second argument.
            if (testElement.classList.contains("c3")) {
                var _toggle = DOMTokenList.prototype.toggle;

                DOMTokenList.prototype.toggle = function (token, force) {
                    if (1 in arguments && !this.contains(token) === !force) {
                        return force;
                    } else {
                        return _toggle.call(this, token);
                    }
                };

            }

            testElement = null;
        }());

    }

}

If you're using jQuery, you can use toggleClass():

function ShowHideDtls(itId) {
    $('#' + itId).toggleClass('in');
}

Edit

If you still want to use regex:

if (/\bin\b/.test(subMen.className))
    subMen.className.replace(/\bin\b/, '');
} else {
    subMen.className += " in";
}

You can also use split() and indexOf as follow to check if a class is present on element.

var classes = className.split(/\s+/),
    classIndex = classes.indexOf('in');
if (classIndex > -1) {
    classes.splice(classIndex, 1);
    subMen.className = classes.join(' ');
} else {
    subMen.className += " in";
}
Tushar
  • 85,780
  • 21
  • 159
  • 179
  • Thank you Tushar. I saw the option of using classlist on the link I mentioned in my question. However it's stated there also that it's not supported by IE previous to IE10 so I preferred not using it. – Dov Miller Aug 11 '15 at 11:51
  • @DovMiller You can check the last code snippet in the answer. It uses array and string methods to add/remove class – Tushar Aug 12 '15 at 03:51
  • Interesting. Can You add an explanation. What do splice and join do? What are their parameters? Does this work in all browsers? Thanks – Dov Miller Aug 12 '15 at 08:13
1

replace function returns the resultant value, it do not assign value indirectly.

So do following:

function ShowHideDtls(itId) {
        var subMen = document.getElementById(itId);
        if (subMen != null) {
            if (subMen.className.match(/(?:^|\s)in(?!\S)/)) {
                subMen.className = subMen.className.replace(/(?:^|\s)in(?!\S)/g, '');
            }
            else {
                subMen.className += " in";
            }
        }
    }
Sachin Chandil
  • 17,133
  • 8
  • 47
  • 65