How to treat all matching elements as a set? - javascript

Is there a way to convert, tweak, treat elements containing the same class as a set?
<div class="f">fgfgh</div>
<div class="f">dfg</div>
<div class="f">qzer</div>
<script>
function _(c){
var x = document.getElementsByClassName(c);
return x;
};
_("f")[all_of_them_not_0_1].innerHTML = "changed";
</script>
I could definitely loop through them, but that's not what I want.

This isn't about JavaScript, but rather the DOM API.
No, there aren't set-based mutation operations (setting innerHTML, for instance) built into the DOM API. You have to loop through the elements (or use something that does so for you behind the scenes, like jQuery).

One idea would be to add Element.prototype methods to HTMLCollection.prototype. However, be aware of that!!
var elementMethods = Object.getOwnPropertyNames(Element.prototype);
elementMethods.forEach(attachElementMethodsToHTMLCollection);
function attachElementMethodsToHTMLCollection(methodName) {
HTMLCollection.prototype[methodName] = function() {
var result = [];
for (var i = 0; i < this.length; i++) {
if (methodName !== 'innerHTML' && typeof Element.prototype[methodName] === 'function') {
result.push(Element.prototype[methodName].apply(this.item(i), arguments))
} else {
result.push(arguments.length ? this.item(i)[methodName] = arguments[0] : this.item(i)[methodName])
}
}
return result;
};
};
Then:
function _(c) {
return document.getElementsByClassName(c);
};
_("f").innerHTML('HTML !')
_("f").setAttribute('title', 'My title')
Demo: https://jsfiddle.net/iRbouh/a7yuzz3z/

Could you not add a class to them like class="f changed" and then with css:
.f.changed:after {
content:'changed';
display:block;
/* any other stylings you need for this */
}
Then all you need to do is add or remove the class when you want to toggle the text? Not sure what you have against looping, but this is sort of one way.

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;
}

Using querySelectorAll to get ALL elements with that class name, not only the first

I've ditched jquery about 9(ish) months ago and needed a selector engine (without all the hassle and don't mind ie<7 support) so i made a simplified version of document.querySelectorAll by creating this function:
// "qsa" stands for: "querySelectorAll"
window.qsa = function (el) {
var result = document.querySelectorAll(el)[0];
return result;
};
This works perfectly fine for 95% of the time but I've had this problem for a while now and i have researched mdn, w3c, SO and not to forget Google :) but have not yet found the answer as to why I only get the first element with the requested class.
And I know that only the first element being returned is caused by the "[0]" at the end, but the function won't work if I remove it so I've tried to make a for loop with an index variable that increases in value depending on the length of elements with that class like this:
window.qsa = function (el) {
var result, el = document.querySelectorAll(el);
for(var i = 0; i < el.length; ++i) {
result = el[i];
}
return result;
};
Again that did not work so I tried a while loop like this:
window.qsa = function (el) {
var result, i = 0, el = document.querySelectorAll(el);
while(i < el.length) {
i++;
}
result = el[i];
return result;
};
By now I'm starting to wonder if anything works? and I'm getting very frustrated with document.querySelectorAll...
But my stubborn inner-self keeps going and I keep on failing (tiering cycle) so I know that now is REALLY the time to ask these questions :
Why is it only returning the first element with that class and not all of them?
Why does my for loop fail?
Why does my while loop fail?
And thank you because any / all help is much appreciated.
Why is it only returning the first element with that class and not all of them?
Because you explicitly get the first element off the results and return that.
Why does my for loop fail?
Because you overwrite result with a new value each time you go around the end of loop. Then you return the last thing you get.
Why does my while loop fail?
The same reason.
If you want all the elements, then you just get the result of running the function:
return document.querySelectorAll(el)
That will give you a NodeList object containing all the elements.
Now that does what you say you want, I'm going to speculate about what your real problem is (i.e. why you think it doesn't work).
You haven't shown us what you do with the result of running that function, but my guess is that you are trying to treat it like an element.
It isn't an element. It is a NodeList, which is like an array.
If you wanted to, for instance, change the background colour of an element you could do this:
element.style.backgroundColor = "red";
If you want to change the background colour of every element in a NodeList, then you have to change the background colour of each one in turn: with a loop.
for (var i = 0; i < node_list.length; i++) {
var element = node_list[i];
element.style.backgroundColor = "red";
}
You are returning a single element. You can return the array. If you want to be able to act on all elements at once, jQuery style, you can pass a callback into your function;
window.qsa = function(query, callback) {
var els = document.querySelectorAll(query);
if (typeof callback == 'function') {
for (var i = 0; i < els.length; ++i) {
callback.call(els[i], els[i], i);
}
}
return els;
};
qsa('button.change-all', function(btn) {
// You can reference the element using the first parameter
btn.addEventListener('click', function(){
qsa('p', function(p, index){
// Or you can reference the element using `this`
this.innerHTML = 'Changed ' + index;
});
});
});
qsa('button.change-second', function(btn) {
btn.addEventListener('click', function(){
var second = qsa('p')[1];
second.innerHTML = 'Changed just the second one';
});
});
<p>One</p>
<p>Two</p>
<p>Three</p>
<button class='change-all'>Change Paragraphs</button>
<button class='change-second'>Change Second Paragraph</button>
Then you can call either use the callback
qsa('P', function(){
this.innerHTML = 'test';
});
Or you can use the array that is returned
var pList = qsa('p');
var p1 = pList[0];
This loop
for(var i = 0; i < el.length; ++i) {
result = el[i];
}
overwrites your result variable every time. That's why you always get only one element.
You can use the result outside though, and iterate through it. Kinda like
var result = window.qsa(el)
for(var i = 0; i < result.length; ++i) {
var workOn = result[i];
// Do something with workOn
}

Is there a way to add/remove several classes in one single instruction with classList?

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;
}

How do I find if an element contains a specific class with jQuery?

I need to check if an element contains a certain child class using JQUERY.
I tried:
if ($('#myElement').has('.myClass')) {
do work son
}
Didn't work.
My html code is laid out like this:
<div id="myElement">
<img>
<span>something</span>
<span class="myClass">Hello</span>
</div>
The easiest way would be to search for .myClass as a child of #myElement:
if($('#myElement .myClass')).length > 0)
If you only want first level children, you'd use >
if($('#myElement > .myClass')).length > 0)
Another way would be passing a selector to find and checking for any results:
if($('#myElement').find('.myClass').length > 0)
Or for first level children only:
if($('#myElement').children('.myClass').length > 0)
Just use QS
var hasClass = document.getElementById("myElement").querySelector(".myClass");
or you could recurse over the children
var element = document.getElementById("myElement");
var hasClass = recursivelyWalk(element.childNodes, function hasClass(node) {
return node.classList.contains("myClass");
});
function recursivelyWalk(nodes, cb) {
for (var i = 0, len = nodes.length; i < len; i++) {
var node = nodes[i];
var ret = cb(node);
if (ret) {
return ret;
}
if (node.childNodes && node.childNodes.length) {
var ret = recursivelyWalk(node.childNodes, cb);
if (ret) {
return ret;
}
}
}
}
Using recursivelyWalk and .classList (which can be shimmed).
Alternatively you can use jQuery
$("#myElement .myClass").hasClass("myClass");
or if you want composite operations without jQuery then try NodeComposite
NodeComposite.$("#myElement *").classList.contains("myClass");
Try:
if($('#myElement').children('.myClass').length) {
// Do what you need to
}
The jQuery object returns an array, which has the .length property. The above code checks if there are any .myClass children in #myElement and, if there are (when .length isn't 0), executes the code inside the if() statement.
Here's a more explicit version:
if($('#myElement').children('.myClass').length > 0) {
// Do what you need to
}
You could always use $('#myElement .myClass').length too, but $.children() is clearer to some. To find elements that aren't direct children, use $.find() in place of $.children().
if($.contains($('#myElement'), $('.myClass'))){
alert("True");
}
else{alert("False")};

Get a CSS value from external style sheet with Javascript/jQuery

Is it possible to get a value from the external CSS of a page if the element that the style refers to has not been generated yet? (the element is to be generated dynamically).
The jQuery method I've seen is $('element').css('property');, but this relies on element being on the page. Is there a way of finding out what the property is set to within the CSS rather than the computed style of an element?
Will I have to do something ugly like add a hidden copy of the element to my page so that I can access its style attributes?
With jQuery:
// Scoping function just to avoid creating a global
(function() {
var $p = $("<p></p>").hide().appendTo("body");
console.log($p.css("color"));
$p.remove();
})();
p {color: blue}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Using the DOM directly:
// Scoping function just to avoid creating a global
(function() {
var p = document.createElement('p');
document.body.appendChild(p);
console.log(getComputedStyle(p).color);
document.body.removeChild(p);
})();
p {color: blue}
Note: In both cases, if you're loading external style sheets, you'll want to wait for them to load in order to see their effect on the element. Neither jQuery's ready nor the DOM's DOMContentLoaded event does that, you'd have to ensure it by watching for them to load.
Normally you should be let the browser apply all the rules and then ask the browser for the results, but for the rare case where you really need to get the value out of the style sheet you can use this: (JSFiddle)
function getStyleSheetPropertyValue(selectorText, propertyName) {
// search backwards because the last match is more likely the right one
for (var s= document.styleSheets.length - 1; s >= 0; s--) {
var cssRules = document.styleSheets[s].cssRules ||
document.styleSheets[s].rules || []; // IE support
for (var c=0; c < cssRules.length; c++) {
if (cssRules[c].selectorText === selectorText)
return cssRules[c].style[propertyName];
}
}
return null;
}
alert(getStyleSheetPropertyValue("p", "color"));
Note that this is pretty fragile, as you have to supply the full selector text that matches the rule you are looking up (it is not parsed) and it does not handle duplicate entries or any kind of precedence rules. It's hard for me to think of a case when using this would be a good idea, but here it is just as an example.
In response to Karim79, I just thought I'd toss out my function version of that answer. I've had to do it several times so this is what I wrote:
function getClassStyles(parentElem, selector, style){
elemstr = '<div '+ selector +'></div>';
var $elem = $(elemstr).hide().appendTo(parentElem);
val = $elem.css(style);
$elem.remove();
return val;
}
val = getClassStyles('.container:first', 'class="title"', 'margin-top');
console.warn(val);
This example assumes you have and element with class="container" and you're looking for the margin-top style of the title class in that element. Of course change up to fit your needs.
In the stylesheet:
.container .title{ margin-top:num; }
Let me know what you think - Would you modify it, and if so how? Thanks!
I have written a helper function that accepts an object with the css attributes to be retrieved from the given css class and fills in the actual css attribute values.
Example is included.
function getStyleSheetValues(colScheme) {
var tags='';
var obj= colScheme;
// enumerate css classes from object
for (var prop in obj) {
if (obj.hasOwnProperty(prop) && typeof obj[prop]=="object") {
tags+= '<span class="'+prop+'"></span>';
}
}
// generate an object that uses the given classes
tags= $('<div>'+tags+'</div>').hide().appendTo("body");
// read the class properties from the generated object
var idx= 0;
for (var prop in obj) {
if (obj.hasOwnProperty(prop) && typeof obj[prop]=="object") {
var nobj= obj[prop];
for (var nprop in nobj) {
if (nobj.hasOwnProperty(nprop) && typeof(nobj[nprop])=="string") {
nobj[nprop]= tags.find("span:eq("+idx+")").css(nobj[nprop]);
}
}
idx++;
}
}
tags.remove();
}
// build an object with css class names where each class name contains one
// or more properties with an arbitrary name and the css attribute name as its value.
// This value will be replaced by the actual css value for the respective class.
var colorScheme= { chart_wall: {wallColor:'background-color',wallGrid:'color'}, chart_line1: { color:'color'} };
$(document).ready(function() {
getStyleSheetValues(colorScheme);
// debug: write the property values to the console;
if (window.console) {
var obj= colorScheme;
for (var prop in obj) {
if (obj.hasOwnProperty(prop) && typeof obj[prop]=="object") {
var nobj= obj[prop];
for (var nprop in nobj) {
if (nobj.hasOwnProperty(nprop)) {
console.log(prop+'.'+nprop +':'+ nobj[nprop]);
}
}
}
}
// example of how to read an individual css attribute value
console.log('css value for chart_wall.wallGrid: '+colorScheme.chart_wall.wallGrid);
}
});
I wrote this js function, seems to be working for nested classes as well:
usage:
var style = get_css_property('.container-class .sub-container-class .child-class', 'margin');
console.log('style');
function get_css_property(class_name, property_name){
class_names = class_name.split(/\s+/);
var container = false;
var child_element = false;
for (var i = class_names.length - 1; i >= 0; i--) {
if(class_names[i].startsWith('.'))
class_names[i] = class_names[i].substring(1);
var new_element = $.parseHTML('<div class="' + class_names[i] + '"></div>');
if(!child_element)
child_element = new_element;
if(container)
$(new_element).append(container);
container = new_element;
}
$(container).hide().appendTo('body');
var style = $(child_element).css(property_name);
$(container).remove();
return style;
}

Categories