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;
}
I'm trying to compare the variable determineHour against the array stationRentalsHours, whenever the variable would be equal to a stationRentalsHours element, I'd like to add that element to another Array (stationRentalsHoursTemp), but only the values that match. I tried with simple operators, but that doesn't put anything into the temp array. I also tried using JQuery $.inArray, but that gives me some strange results, Equal to those in the original array. Are there any other methods of comparing a variable with an array for this particular task?
Thank you for any help.
function updateChart() {
if(canvas3){canvas3.destroy();}
var determineHour = selectNumber.options[selectNumber.selectedIndex].innerHTML;
for (var i = 0; i < stationRentalsHours.length; i++) {
/*if(determineHour == stationRentalsHours){
stationRentalsHoursTemp.push(stationRentalsHours[i]);*/
if( $.inArray(determineHour, stationRentalsHours[i])){
stationRentalsHoursTemp.push(stationRentalsHours[i]);
}
}
In this case, instead of using $.inArray, you can simply use the for loop and the index to test the equality. I guess you mixed up two things:
var determineHour = selectNumber.options[selectNumber.selectedIndex].innerHTML;
for (var i = 0; i < stationRentalsHours.length; i++) {
if( determineHour == stationRentalsHours[i]){
stationRentalsHoursTemp.push(stationRentalsHours[i]);
}
}
Better yet, use filter:
var determineHour = selectNumber.options[selectNumber.selectedIndex].innerHTML;
stationRentalsHoursTemp = stationRentalsHours.filter(function(val){return val == determineHour;});
Instead of
if( $.inArray(determineHour, stationRentalsHours[i])){
Try
if( $.inArray(determineHour, stationRentalsHours) != -1){
Your commented out code would do the trick with a slight amendment to the if condition. Your original condition was comparing a string to an array instead of an individual element in that array:
function updateChart() {
if(canvas3){
canvas3.destroy();
}
var determineHour = selectNumber.options[selectNumber.selectedIndex].innerHTML;
for (var i = 0; i < stationRentalsHours.length; i++){
if(determineHour == stationRentalsHours[i]){
stationRentalsHoursTemp.push(stationRentalsHours[i]);
}
}
}
I'm very new at recursion, and have been tasked with writing getElementsByClassName in JavaScript without libraries or the DOM API.
There are two matching classes, one of which is in the body tag itself, the other is in a p tag.
The code I wrote isn't working, and there must be a better way to do this. Your insight would be greatly appreciated.
var elemByClass = function(className) {
var result = [];
var nodes = document.body; //<body> is a node w/className, it needs to check itself.
var childNodes = document.body.childNodes; //then there's a <p> w/className
var goFetchClass = function(nodes) {
for (var i = 0; i <= nodes; i++) { // check the parent
if (nodes.classList == className) {
result.push(i);
console.log(result);
}
for (var j = 0; j <= childNodes; j++) { // check the children
if (childNodes.classList == className) {
result.push(j);
console.log(result);
}
goFetchClass(nodes); // recursion for childNodes
}
goFetchClass(nodes); // recursion for nodes (body)
}
return result;
};
};
There are some errors, mostly logical, in your code, here's what it should have looked like
var elemByClass = function(className) {
var result = [];
var pattern = new RegExp("(^|\\s)" + className + "(\\s|$)");
(function goFetchClass(nodes) {
for (var i = 0; i < nodes.length; i++) {
if ( pattern.test(nodes[i].className) ) {
result.push(nodes[i]);
}
goFetchClass(nodes[i].children);
}
})([document.body]);
return result;
};
Note the use of a regex instead of classList, as it makes no sense to use classList which is IE10+ to polyfill getElementsByClassName
Firstly, you'd start with the body, and check it's className property.
Then you'd get the children, not the childNodes as the latter includes text-nodes and comments, which can't have classes.
To recursively call the function, you'd pass the children in, and do the same with them, check for a class, get the children of the children, and call the function again, until there are no more children.
Here are some reasons:
goFetchClass needs an initial call after you've defined it - for example, you need a return goFetchClass(nodes) statement at the end of elemByClass function
the line for (var i = 0; i <= nodes; i++) { will not enter the for loop - did you mean i <= nodes.length ?
nodes.classList will return an array of classNames, so a direct equality such as nodes.classList == className will not work. A contains method is better.
Lastly, you may want to reconsider having 2 for loops for the parent and children. Why not have 1 for loop and then call goFetchClass on the children? such as, goFetchClass(nodes[i])?
Hope this helps.
I have two filters, findEdited and getUnitIndex. They both do exactly the same thing (find the index of an element in an array), but in different parts of an array. I would like to combine them into one filter, getIndex.
Here's the first one:
myApp.filter('findEdited', function(){
return function(food, foods) {
for (var index in foods) {
if (foods[index].uid == food.uid) {
return index;
}
}
return -1;
}
});
In the controller:
var index = $filter('findEdited')(food, $scope.editedFood);
And the second one:
myApp.filter('getUnitIndex', function () {
return function(list, item) {
for( var i = 0; i < list.length; i++ ) {
if( list[i].gram == item.gram ) {
return(i);
}
}
return(-1);
}
});
Controller:
var unitIndex = $filter('getUnitIndex')(food.unit, $scope.editedFood[index].unit.selected);
As near as I can tell, the only functional difference between them is the .uid & .gram identifier, which is telling the loop which part of the object to match. I've tried to rewrite these into one filter, like this, with ref standing in for this identifier:
myApp.filter('findIndex', function () {
return function(list, item, ref) {
for( var i = 0; i < list.length; i++ ) {
if( list[i].ref == item.ref ) {
return(i);
}
}
return(-1);
}
});
And called like this, if I want ref to be uid:
var unitIndex = $filter('findIndex')(food.unit, $scope.editedFood[index].unit.selected, 'uid');
This doesn't work. The example above returns 0 on every run. Any suggestions on how to pass the desired reference to this filter so that I can use it generically to find the index of any array item in any array?
Plunkr
Update
I can't get this to work for the filter "findEdited". I have written my generic filter like this:
myApp.filter('getIndex', function(){
return function(list, item, ref) {
for (var index in list) {
if (list[index][ref] == item[ref]) {
return index;
}
}
return -1;
}
});
Which works if call it like this, to find the index of a food unit by matching 'gram':
var unitIndex = $filter('getIndex')(food.unit, $scope.editedFood[index].unit.selected, 'gram');
But it doesn't work if I call it like this, to find out if a food unit exists in the array editedFood:
var foodIndex = $filter('getIndex')(food, $scope.editedFood, 'uid');
I can see that I am passing in different search objects & search contexts, but the foodIndex search works if I pass it to the almost-identical filter findEdited filter above. Any ideas why?
Here's an updated Plunkr.
You must use the array-like notation here (you can use it for objects as well). item.ref would mean the property called ref, while item[ref] will mean the property called whatever the expression in the [] evaluates to (in this case, whatever is stored in the variable called ref).
if( list[i][ref] == item[ref] ) {
In other words, writing item.ref is equivalent to writing item['ref'].
I'm trying to set up an IF statement if a value is contained within an array.
I've found some code which claimed to work but it doesn't seem to be.
var myAsi = ['01','02','24OR01','30De01','9thC01','A.Hu01','A01','AACAMSTE','ABBo01','ABBo02','ABC-01','ACCE01','Acce02','AceR01','h+dm01','Merr02','Ofak01','Wage01','Youn01'];
Array.prototype.find = function(searchStr) {
var returnArray = false;
for (i=0; i<this.length; i++) {
if (typeof(searchStr) == 'function') {
if (searchStr.test(this[i])) {
if (!returnArray) { returnArray = [] }
returnArray.push(i);
}
} else {
if (this[i]===searchStr) {
if (!returnArray) { returnArray = [] }
returnArray.push(i);
}
}
}
return returnArray;
}
var resultHtml = '';
resultHtml+='<table style ="width: 400px">';
resultHtml+='<tr colspan="2">';
resultHtml+='<td colspan="2">';
resultHtml+='<b><font color = "Red">(Client Code)</font><br><font color = "green">(Company Name)</font></b>';
resultHtml+='</td>';
resultHtml+='</tr>';
$.each(data, function(i,item){
resultHtml+='<div class="result">';
resultHtml+='<tr>';
if (notFound=myAsi.find("'"+item.code+"'") == false) {
resultHtml+='<td>';
}
else {
resultHtml+='<td bgcolor=#D8D8D8>';
}
resultHtml+='<font color = "red">'+item.code+'</font><br>';
resultHtml+='<font color = "green">'+item.content+'</font></td>';
resultHtml+='<td style ="width: 80px">Remove - ';
resultHtml+='Add';
resultHtml+='</td>';
resultHtml+='</tr>';
resultHtml+='</div>';
});
resultHtml+='</table>';
The item.code cycles through and I need an IF statement to tell me if it appears within the array.
Any help would be great.
If you only want to find if an item is in an array you could use a simpler function than that. For eg. the jQuery implementation:
// returns index of the element or -1 if element not present
function( elem, array ) {
if ( array.indexOf ) {
return array.indexOf( elem );
}
for ( var i = 0, length = array.length; i < length; i++ ) {
if ( array[ i ] === elem ) {
return i;
}
}
return -1;
},
This uses the native browser implementation of indexOf if available (all browsers except IE I think), otherwise a manual loop.
Try removing the apostrophes from your find() call. eg
notFound=myAsi.find(item.code)
Though actually, for your purposes see this example which uses this function....
Array.prototype.find = function(searchStr) {
for (var i=0; i<this.length; i++) {
if (this[i]==searchStr) return true;
};
return false;
};
And as an aside - Be very careful about using var before using a variable - otherwise you create a global variable (which you probably don't want). ie the line in your original function....
for (i=0; i<this.length; i++)
i is now global...
Array.prototype.contains = function(value, matcher) {
if (!matcher || typeof matcher !== 'function') {
matcher = function(item) {
return item == value;
}
}
for (var i = 0, len = this.length; i < len; i++) {
if (matcher(this[i])) {
return true;
}
}
return false;
};
This returns true for elements in the array that statisfy the conditions defined in matcher. Implement like this:
var arr = ['abc', 'def', 'ghi']; // the array
var valueToFind= 'xyz'; // a value to find in the array
// a function that compares an array item to match
var matcher = function(item) {
return item === matchThis;
};
// is the value found?
if (arr.contains(valueToFind, matcher)) {
// item found
} else {
// item not found
}
UPDATES:
Changed the contains method to take a value and an optional matcher function. If no matcher is included, it will do a simple equality check.
Test this on jsFiddle.net: http://jsfiddle.net/silkster/wgkru/3/
You could just use the builtin function
['a','b','c'].indexOf('d') == -1
This behavior was mandated in the javascript specification from over 6 years ago. Though I gave up on Internet Explorer for these reasons at around IE8, because of this incredibly poor support for standards. If you care about supporting very old browsers, you can use http://soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer/ to tack on your own custom Array.indexOf
I don't recall IE9 supporting [].indexOf, but Microsoft claims it does: http://msdn.microsoft.com/en-us/library/ff679977(v=VS.94).aspx
The standard way to determine the index of the first occurence of a given value in an array is the indexOf method of Array objects.
This code checks if it this method is supported, and implements it if not, so that it is available on any Array object:
if(Array.prototype.indexOf==null)
Array.prototype.indexOf = function(x){
for(var i=0, n=this.length; i<n; i++)if(this[i]===x)return i;
return -1;
};
Now myArray.indexOf(myValue) returns the first index of myValue in myArray, or -1 if not found.