What is the best way to check if element has a class? - javascript

The problem
If the element has multiple classes then it will not match with the regular property value checking, so I'm looking for the best way to check if the object has a particular class in the element's className property.
Example
// element's classname is 'hello world helloworld'
var element = document.getElementById('element');
// this obviously fails
if(element.className == 'hello'){ ... }
// this is not good if the className is just 'helloworld' because it will match
if(element.className.indexOf('hello') != -1){ ... }
So what would be the best way to do this?
just pure javascript please

function hasClass( elem, klass ) {
return (" " + elem.className + " " ).indexOf( " "+klass+" " ) > -1;
}

In modern browsers, you can use classList:
if (element.classList.contains("hello")) {
// do something
}
In the browser that doesn't implement classList but exposes the DOM's prototype, you can use the shim showed in the link.
Otherwise, you can use the same shim's code to have a generic function without manipulate the prototype.

this 2018 use ES6
const hasClass = (el, className) => el.classList.contains(className);
How to use
hasClass(document.querySelector('div.active'), 'active'); // true

You ask for pure javascript, so this is how jQuery implement it:
hasClass: function( selector ) {
var className = " " + selector + " ",
i = 0,
l = this.length;
for ( ; i < l; i++ ) {
if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
return true;
}
}
return false;
},
rclass is a regular expression of [\n\t\r], to ensure that alternative methods of delimiting class names are not an issue. this would be jQuery's reference to the object(s) and in a sense it makes the code more complicated than required, but it should make sense without knowing the details of it.

This should work for you:
var a = [];
function getElementsByClassName(classname, node) {
if(!node) node = document.getElementsByTagName("body")[0];
var re = new RegExp('\\b' + classname + '\\b');
var els = node.getElementsByTagName("*");
for(var i=0, j=els.length; i<j; i++)
if(re.test(els[i].className)) a.push(els[i]);
return a;
}
getElementsByClassName('wrapper');
for (var i=0; i< a.length; i++) {
console.log(a[i].className);
}
This code will traverse the DOM and search for classes defined as parameter in the main function.Then it will test each founded class elements with a regexp pattern. If it founds will push to an array, and will output the results.

First, split the className by using the " " character, then check the index of the class you want to find.
function hasClass(element, clazz) {
return element.className.split(" ").indexOf(clazz) > -1;
}

Related

How to remove the same class from multiple elements? [duplicate]

So far I have to do this:
elem.classList.add("first");
elem.classList.add("second");
elem.classList.add("third");
While this is doable in jQuery, like this
$(elem).addClass("first second third");
I'd like to know if there's any native way to add or remove.
elem.classList.add("first");
elem.classList.add("second");
elem.classList.add("third");
is equal
elem.classList.add("first","second","third");
The new spread operator makes it even easier to apply multiple CSS classes as array:
const list = ['first', 'second', 'third'];
element.classList.add(...list);
You can do like below
Add
elem.classList.add("first", "second", "third");
// OR
elem.classList.add(...["first","second","third"]);
Remove
elem.classList.remove("first", "second", "third");
// OR
elem.classList.remove(...["first","second","third"]);
Reference
TLDR;
In the straight forward case above removal should work. But in case of removal, you should make sure class exists before you remove them
const classes = ["first","second","third"];
classes.forEach(c => {
if (elem.classList.contains(c)) {
element.classList.remove(c);
}
})
The classList property ensures that duplicate classes are not unnecessarily added to the element. In order to keep this functionality, if you dislike the longhand versions or jQuery version, I'd suggest adding an addMany function and removeMany to DOMTokenList (the type of classList):
DOMTokenList.prototype.addMany = function(classes) {
var array = classes.split(' ');
for (var i = 0, length = array.length; i < length; i++) {
this.add(array[i]);
}
}
DOMTokenList.prototype.removeMany = function(classes) {
var array = classes.split(' ');
for (var i = 0, length = array.length; i < length; i++) {
this.remove(array[i]);
}
}
These would then be useable like so:
elem.classList.addMany("first second third");
elem.classList.removeMany("first third");
Update
As per your comments, if you wish to only write a custom method for these in the event they are not defined, try the following:
DOMTokenList.prototype.addMany = DOMTokenList.prototype.addMany || function(classes) {...}
DOMTokenList.prototype.removeMany = DOMTokenList.prototype.removeMany || function(classes) {...}
Since the add() method from the classList just allows to pass separate arguments and not a single array, you need to invoque add() using apply. For the first argument you will need to pass the classList reference from the same DOM node and as a second argument the array of classes that you want to add:
element.classList.add.apply(
element.classList,
['class-0', 'class-1', 'class-2']
);
Here is a work around for IE 10 and 11 users that seemed pretty straight forward.
var elem = document.getElementById('elem');
['first','second','third'].forEach(item => elem.classList.add(item));
<div id="elem">Hello World!</div>
Or
var elem = document.getElementById('elem'),
classes = ['first','second','third'];
classes.forEach(function(item) {
elem.classList.add(item);
});
<div id="elem">Hello World!</div>
To add class to a element
document.querySelector(elem).className+=' first second third';
UPDATE:
Remove a class
document.querySelector(elem).className=document.querySelector(elem).className.split(class_to_be_removed).join(" ");
Newer versions of the DOMTokenList spec allow for multiple arguments to add() and remove(), as well as a second argument to toggle() to force state.
At the time of writing, Chrome supports multiple arguments to add() and remove(), but none of the other browsers do. IE 10 and lower, Firefox 23 and lower, Chrome 23 and lower and other browsers do not support the second argument to toggle().
I wrote the following small polyfill to tide me over until support expands:
(function () {
/*global DOMTokenList */
var dummy = document.createElement('div'),
dtp = DOMTokenList.prototype,
toggle = dtp.toggle,
add = dtp.add,
rem = dtp.remove;
dummy.classList.add('class1', 'class2');
// Older versions of the HTMLElement.classList spec didn't allow multiple
// arguments, easy to test for
if (!dummy.classList.contains('class2')) {
dtp.add = function () {
Array.prototype.forEach.call(arguments, add.bind(this));
};
dtp.remove = function () {
Array.prototype.forEach.call(arguments, rem.bind(this));
};
}
// Older versions of the spec didn't have a forcedState argument for
// `toggle` either, test by checking the return value after forcing
if (!dummy.classList.toggle('class1', true)) {
dtp.toggle = function (cls, forcedState) {
if (forcedState === undefined)
return toggle.call(this, cls);
(forcedState ? add : rem).call(this, cls);
return !!forcedState;
};
}
})();
A modern browser with ES5 compliance and DOMTokenList are expected, but I'm using this polyfill in several specifically targeted environments, so it works great for me, but it might need tweaking for scripts that will run in legacy browser environments such as IE 8 and lower.
A very simple, non fancy, but working solution that I would have to believe is very cross browser:
Create this function
function removeAddClasses(classList,removeCollection,addCollection){
for (var i=0;i<removeCollection.length;i++){
classList.remove(removeCollection[i]);
}
for (var i=0;i<addCollection.length;i++){
classList.add(addCollection[i]);
}
}
Call it like this:
removeAddClasses(node.classList,arrayToRemove,arrayToAdd);
...where arrayToRemove is an array of class names to remove:
['myClass1','myClass2'] etcetera
...and arrayToAdd is an array of class names to add:
['myClass3','myClass4'] etcetera
The standard definiton allows only for adding or deleting a single class. A couple of small wrapper functions can do what you ask :
function addClasses (el, classes) {
classes = Array.prototype.slice.call (arguments, 1);
console.log (classes);
for (var i = classes.length; i--;) {
classes[i] = classes[i].trim ().split (/\s*,\s*|\s+/);
for (var j = classes[i].length; j--;)
el.classList.add (classes[i][j]);
}
}
function removeClasses (el, classes) {
classes = Array.prototype.slice.call (arguments, 1);
for (var i = classes.length; i--;) {
classes[i] = classes[i].trim ().split (/\s*,\s*|\s+/);
for (var j = classes[i].length; j--;)
el.classList.remove (classes[i][j]);
}
}
These wrappers allow you to specify the list of classes as separate arguments, as strings with space or comma separated items, or a combination. For an example see http://jsfiddle.net/jstoolsmith/eCqy7
Assume that you have an array of classes to being added, you can use ES6 spread syntax:
let classes = ['first', 'second', 'third'];
elem.classList.add(...classes);
A better way to add the multiple classes separated by spaces in a string is using the Spread_syntax with the split:
element.classList.add(...classesStr.split(" "));
I found a very simple method which is more modern and elegant way.
const el = document.querySelector('.m-element');
// To toggle
['class1', 'class2'].map((e) => el.classList.toggle(e));
// To add
['class1', 'class2'].map((e) => el.classList.add(e));
// To remove
['class1', 'class2'].map((e) => el.classList.remove(e));
Good thing is you can extend the class array or use any coming from API easily.
I liked #rich.kelly's answer, but I wanted to use the same nomenclature as classList.add() (comma seperated strings), so a slight deviation.
DOMTokenList.prototype.addMany = DOMTokenList.prototype.addMany || function() {
for (var i = 0; i < arguments.length; i++) {
this.add(arguments[i]);
}
}
DOMTokenList.prototype.removeMany = DOMTokenList.prototype.removeMany || function() {
for (var i = 0; i < arguments.length; i++) {
this.remove(arguments[i]);
}
}
So you can then use:
document.body.classList.addMany('class-one','class-two','class-three');
I need to test all browsers, but this worked for Chrome.
Should we be checking for something more specific than the existence of DOMTokenList.prototype.addMany? What exactly causes classList.add() to fail in IE11?
Another polyfill for element.classList is here. I found it via MDN.
I include that script and use element.classList.add("first","second","third") as it's intended.
One of the best solution is to check if an element exists and then proceed to add or possibly remove and above all if the element is empty, delete it.
/**
* #description detect if obj is an element
* #param {*} obj
* #returns {Boolean}
* #example
* see below
*/
function isElement(obj) {
if (typeof obj !== 'object') {
return false
}
let prototypeStr, prototype
do {
prototype = Object.getPrototypeOf(obj)
// to work in iframe
prototypeStr = Object.prototype.toString.call(prototype)
// '[object Document]' is used to detect document
if (
prototypeStr === '[object Element]' ||
prototypeStr === '[object Document]'
) {
return true
}
obj = prototype
// null is the terminal of object
} while (prototype !== null)
return false
}
/*
* Add multiple class
* addClasses(element,['class1','class2','class3'])
* el: element | document.querySelector(".mydiv");
* classes: passing:: array or string : [] | 'cl1,cl2' | 'cl1 cl2' | 'cl1|cl2'
*/
function addClasses(el, classes) {
classes = Array.prototype.slice.call(arguments, 1);
if ( isElement(el) ){ //if (document.body.contains(el)
for (var i = classes.length; i--;) {
classes[i] = Array.isArray(classes[i]) ? classes[i]: classes[i].trim().split(/\s*,\s*|\s+/);
for (var j = classes[i].length; j--;)
el.classList.add(classes[i][j]);
}
}
}
/*
* Remove multiple class
* Remove attribute class is empty
* addClasses(element,['class1','class2','class3'])
* el: element | document.querySelector(".mydiv");
* classes: passing:: array or string : [] | 'cl1,cl2' | 'cl1 cl2' | 'cl1|cl2'
*/
function removeClasses(el, classes) {
classes = Array.prototype.slice.call(arguments, 1);
if ( isElement(el) ) {
for (var i = classes.length; i--;) {
classes[i] = Array.isArray(classes[i]) ? classes[i]: classes[i].trim().split(/\s*,\s*|\s+/);
for (var j = classes[i].length; j--;)
el.classList.remove(classes[i][j]);
let cl = el.className.trim();
if (!cl){
el.removeAttribute('class');
}
}
}
}
var div = document.createElement("div");
div.id = 'test'; // div.setAttribute("id", "test");
div.textContent = 'The World';
//div.className = 'class';
// possible use: afterend, beforeend
document.body.insertAdjacentElement('beforeend', div);
// Everything written above you can do so:
//document.body.insertAdjacentHTML('beforeend', '<div id="text"></div>');
var el = document.querySelector("#test");
addClasses(el,'one,two,three,four');
removeClasses(el,'two,two,never,other');
el.innerHTML = 'I found: '+el.className;
// return : I found: four three one
#test {
display: inline-block;
border: 1px solid silver;
padding: 10px;
}

querySelectorAll only selecting second element when using this.element

I've recently started learning about JavaScript objects, and how frameworks such as jQuery, and Modernizr work.
I have tried to create my own little "framework", to further learn how JavaScript objects work, and how to utilize them to their full potential.
It's all gone pretty smoothly so far, until I tried to set a global variable using the querySelectorAll() method (and a for loop), to grab multiple elements with the specified selector.
With this, I intended to add or remove a class from each of those elements with that specific selector. However, it only ever worked on the very last element of the bunch.
Here is my (relevant) JavaScript:
var aj = function(sr){
this.selector = sr || null; // set global selector variable
this.element = null;
}
aj.prototype.init = function(){
switch(this.selector[0]){
// first, second, third case e.t.c...
default:
var els = document.querySelectorAll(this.selector); // select all elements with specified selector (set above)
for(var i = 0; i < els.length; i++){
this.element = els[i];
}
}
};
aj.prototype.class = function(type, classes){
if(type === "add"){ // if the user wants to add a class
if((" " + this.element.className + " ").indexOf(" " + classes + " ") < 0){
this.element.className += " " + classes;
}
} else if(type === "remove") { // if the want to remove a class
var regex = new RegExp("(^| )" + classes + "($| )", "g");
this.element.className = this.element.className.replace(regex, " ");
}
};
Example:
<div class="example-class">Example</div>
<div class="example-class">Example 2</div> <!-- only this one will be altered !-->
<script>
$(".example-class").class("add", "classname");
</script>
Why would this be occurring? My for loop appears to be correct, so I am unsure what is wrong. Apologies if it appears pretty obvious, however, I'm still new to vanilla JavaScript.
All help (and suggestions) is appreciated,
Thanks.
for(var i = 0; i < els.length; i++){
this.element = els[i];
}
You have a loop. Each time it goes around the loop it assigns a value to this.element.
The first time it goes around the loop it assigns the value of els[0]. The second time it assigns the value of els[1].
Since you only have two elements that match, it reaches the end of the loop and stops.
At this point, this.element is still equal to els[1].
If you want to do something (like add membership of a class) to each item in els then you have to loop over els at the time you modify className.

How to check class of multiple class elements in javascript, without jquery

I have the following javascript code to put the uploader name before the upload date and view count in youtube search results.
function hasClass(element, cls) {
return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1;
}
var elems = document.getElementsByClassName('yt-lockup-meta-info');
var elemss = document.getElementsByClassName('yt-uix-sessionlink g-hovercard spf-link');
var elemsss = 1;
var elemssss = 0;
var myElem = document.getElementById('sb-button-notify');
if (myElem == null) elemsss = 0; // these two lines are here to skip the additional element
// that is only there if you are logged in
for (var i = 0; i < elems.length; ++i) {
if (hasClass(elemss[i+elemsss],'yt-uix-tile-link'))
{elemssss=elemssss+1;elemsss=elemsss+3;alert('damn');}
elems[i+elemssss].insertBefore(elemss[i+elemsss],elems[i+elemssss].firstChild);}
Instead of the hasClass function, how can I check the whole class attribute of a multiple class element? This didn't work:
element.className=='yt-uix-sessionlink yt-uix-tile-link
yt-ui-ellipsis yt-ui-ellipsis-2 g-hovercard spf-link'
I just need to check the class, so the code can skip elements with a specific class. An even better solution would be an alternative to document.getElementsByClassName, which would only get elements with exactly the given classes, and ignore ones that have MORE classes.
element.classList
It would return you the array of all the classes present on the element
like ["site-icon", "favicon", "favicon-stackoverflow"] , so then by using normal javascript you can implement hasClass functionality of your own.
So your hasClass function becomes
function hasClass(element, cls){
return element.classList.contains(cls);
}
You can use classList but this is not supported in all browsers.
I think the best solution is something like that:
function hasClass (element, cls) {
var classes = element.className.split(' ');
return classes.indexOf(cls) != -1;
}

How to add additional class name to an element?

I can't believe I can't find this in google - I need to add an additional class name to a div with the classname checkField:
<div id="wth" class="checkField"><label>Ok whatever</label></div>
document.getElementById("wth").className="error"; // This works
document.getElementsByClassName("field").className="error"; // Doesn't work
I tried this:
document.getElementsByClassName("field").getElementsByTagName("label").className="error";
^ Also doesn't work.
Can someone please give me some advice? I'm too use to JQuery.
The getElementsByClassName method returns a nodeList, which is an array-like object, not a single element. To do what you want you need to iterate the list:
var divs = document.getElementsByClassName("checkField");
if (divs) {
// Run in reverse because we're
// modifying the result as we go:
for (var i=divs.length; i-- >= 0;) {
divs[i].className = 'error';
}
}
Also, just setting className actually replaces the class instead of adding to it. If you want to add another class you need to do it like this:
divs[i].className += ' error'; // notice the space bar
As for the second thing you're trying to do, that is setting the class of the label instead of the div, you need to loop through the divs and call getElementsByTagName on them:
var divs = document.getElementsByClassName("checkField");
if (divs) {
// Run in reverse because we're
// modifying the result as we go:
for (var i=divs.length; i-- >= 0;) {
var labels = divs[i].getElementsByTagName('label');
if (labels) {
for (var j=0; j<labels.length; j++) {
labels[j].className = 'error';
}
}
}
}
You need to add the class onto the current value:
document.body.className += " foo"; // adds foo class to body
Some browsers support classList which provides methods for adding, removing, and toggling classes on elements. For instance, you could add a new class like this:
document.body.classList.add("newClass");
Imagine the work involved in toggling a class; you'd have to perform some string operation to first determine whether a class is already on the element or not, and then respond accordingly. With classList you can just use the toggle method:
document.body.classList.toggle("toggleMe");
Some browsers don't currently support classList, but this won't prevent you from taking advantage of it. You can download a polyfill to add this feature where it's not natively supported.
try something like
you want to do this by document.body
so try
var demo =document.body;
// also do this by class like var demo = document.getElementsByClassName("div1one");
// or id var demo = document.getElementsId("div1one");
demo .className = demo .className + " otherclass";
or simpler solution if you have jquery as an option
$("body").addClass("yourClass");
Here's are simple utility functions for adding and removing a class:
function addClass(elem, cls) {
var oldCls = elem.className;
if (oldCls) {
oldCls += " ";
}
elem.className = oldCls + cls;
}
function removeClass(elem, cls) {
var str = " " + elem.className + " ";
elem.className = str.replace(" " + cls + " ", " ").replace(/^\s+|\s+$/g, "");
}

jQuery - has class that starts with

What would be the best way to get all divs that have any class that starts with input? In other words, a and b should be returned from what's below, but not c.
<div id="a" class="input1 foo"></div>
<div id="b" class="foo input2"></div>
<div id="c" class="xinput3 foo"></div>
The ostensible way, which surprisingly was accepted here, is to do $("div[class^='input']"); but of course that misses b. And of course $("div[class*='input']"); will give a false positive on c.
The best I could come up with was this monstrosity
function getAllInputDivs() {
return $("div").filter(function (i, currentDiv) {
return $.grep($(currentDiv).attr("class").split(" "), function (val) {
return val.substr(0, "input".length) === "input";
}).length > 0;
});
}
Is there a cleaner way? Here's a working fiddle of the above
You can create your own expression in jQuery
$.expr[':'].hasClassStartingWithInput = function(obj){
return (/\binput/).test(obj.className);
};
and you can retrieve those div with
$('div:hasClassStartingWithInput');
a JsFiddle Example: http://jsfiddle.net/7zFD6/
Edit: you could also use a parameter (without hardcoding the class name inside the function identifier) in this way
$.expr[':'].hasClassStartingWith = function(el, i, selector) {
var re = new RegExp("\\b" + selector[3]);
return re.test(el.className);
}
new example on http://jsfiddle.net/pMepk/1/
Here's one way...
function getAllInputDivs() {
return $("div").filter(function () {
return /(?:^|\s)input/.test(this.className);
});
}
Or make it more versatile...
function classStartsWith( tag, s ) {
var re = new RegExp('(?:^|\\s)' + s);
return $(tag || '*').filter(function () {
return re.test(this.className);
});
}
Or take the indexOf approach if you don't like regex...
function classStartsWith( tag, s ) {
return $(tag || '*').filter(function () {
return this.className.indexOf(s)===0 || this.className.indexOf(' ' + s)>-1;
});
}
Though you should be aware that it does't test for tab characters, only space characters, so it could fail if a tab was used instead of a space.
Going back to the regex versions, you can improve efficiency by adding the searched string to the selector.
Then it is only testing a subset of divs.
function getAllInputDivs() {
return $("div[class*='input']").filter(function () {
return /(?:^|\s)input/.test(this.className);
});
}
With the .filter() applied to only those divs that you know have input somewhere in the class, the performance will improve.
Or the versatile version would look like this:
function classStartsWith( tag, s ) {
var re = new RegExp('(?:^|\\s)' + s);
return $((tag || '*') + '[class*="' + s + '"]').filter(function () {
return re.test(this.className);
});
}
This is my solution for the problem:
(function($) {
$.fn.hasClassStartsWith = function(klass) {
var _return = [];
for(var i = 0; i < this.length; i++){
if((' ' + $(this[i]).attr('class')).indexOf(klass) != -1)
_return.push(this[i]);
}
return _return;
}
})(jQuery);
Use it as follows:
var divs = $('div').hasClassStartsWith("my_class_prefix");
It works also for the case someone creates a class with a dot in the middle.

Categories