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

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

Related

How to treat all matching elements as a set?

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.

d3 selectAll Source code

I'm trying to understand the source code for d3 selectAll
and I don't understand the line following my comment below.
I can see how there is a closure on the selector string and that this is set to node when d3_selectAll is invoked, but how are the other three arguments in the call consumed?
import "../core/array";
import "selection";
d3_selectionPrototype.selectAll = function(selector) {
var subgroups = [],
subgroup,
node;
selector = d3_selection_selectorAll(selector);
for (var j = -1, m = this.length; ++j < m;) {
for (var group = this[j], i = -1, n = group.length; ++i < n;) {
if (node = group[i]) {
//***where are node.__data__, i, j consumed?***
subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j)));
subgroup.parentNode = node;
}
}
}
return d3_selection(subgroups);
};
function d3_selection_selectorAll(selector) {
return typeof selector === "function" ? selector : function() {
return d3_selectAll(selector, this);
};
}
This is only relevant in the context of subselections and explained in the documentation:
The selector may also be specified as a function that returns an array of elements (or a NodeList), or the empty array if there are no matching elements. In this case, the specified selector is invoked in the same manner as other operator functions, being passed the current datum d and index i , with the this context as the current DOM element.
In particular, if the selector is not a function, these arguments are simply ignored (function declaration at the end with no arguments).

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

Javascript Closure and DOM Builder

I am making a DOM builder which I have working succesfully but now I am trying to assign some shorthand functions so that div() -> e("div")
Here is my code:
//assign these objects to a namespace, defaults to window
(function(parent) {
/**
* Creates string of element
* #param tag The element to add
* #param options An object of attributes to add
* #param child ... n Child elements to nest
* #return HTML string to use with innerHTML
*/
var e = function(tag, options) {
var html = '<'+tag;
var children = Array.prototype.slice.apply(arguments, [(typeof options === "string") ? 1 : 2]);
if(options && typeof options !== "string") {
for(var option in options) {
html += ' '+option+'="'+options[option]+'"';
}
}
html += '>';
for(var child in children) {
html += children[child];
}
html += '</'+tag+'>';
return html;
}
//array of tags as shorthand for e(<tag>) THIS PART NOT WORKING
var tags = "div span strong cite em li ul ol table th tr td input form textarea".split(" "), i=0;
for(; i < tags.length; i++) {
(function(el) { //create closure to keep EL in scope
parent[el] = function() {
var args = Array.prototype.slice.call(arguments);
console.log(args);
args[0] = el; //make the first argument the shorthand tag
return e.apply(e,args);
};
})(tags[i]);
}
//assign e to parent
parent.e = e;
})(window);
What's currently happening is the args array is getting modified each time I call one of the shorthand functions and I assume what needs to happen is a closure somewhere so the args array I created does not get affected each time it is called. Here is the output of the unit tests:
div( div( span("Content")), span()) expected: <div><div><span>Content</span></div><span></span></div> result: <div><span></span></div>
div( div( span( e("b", e("b", e("b")))), span())) expected: <div><div><span><b><b><b></b></b></b></span><span></span></div></div> result: <div></div>
Though this doesn't directly answer your question,
for(var el in tags) {
is not entirely correct. tags is an array, not an object, so its properties cannot be enumerated using for (... in ...). Try
for(var el = 0; el < tags.length; el++) {
This can make a huge difference towards the interpreter's understanding of your code... and the correct execution of your algorithm.
Blonde moment, I was overwriting the first element when I meant to use args.unshift(el);
#MvanGeest - doing a for..in on an array is technically allowed. Arrays are still objects in javascript. The index of the array will be the key if iterated using a for..in loop. Obviously not the point of using an array in that case, but thought I would clarify.
#Anurag - the forEach method is not supported in IE8 (not sure about 9) so that might not be a reliable method to use until later on in the future.

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