Reputation: 3508
I'm building an app and I need to remove all link elements with a specific class from the header. I've built an example of the problem I'm having in the w3schools editor.
For the purposes of reproducing the issue I've added multiple identical links to the header with the class "link-to-remove":
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" class="link-to-remove">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" class="link-to-remove">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" class="link-to-remove">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" class="link-to-remove">
This is the function I'm using to remove the links:
function removeLinks() {
const head = document.getElementsByTagName('head')[0];
const links = head.getElementsByClassName('link-to-remove');
for(let i = 0; i < links.length; i++) {
head.removeChild(links[i]);
}
}
The function has the logic for removing all of the link elements, but it removes only a few every time. Only after pressing the button that triggers this function multiple times are all of the links removed. (In the example all links are removed when the bootstrap table styles disappear)
The example: https://www.w3schools.com/code/tryit.asp?filename=G36L0TRX06V3
What do I need to change in this function for it to remove all of the links the first time it's triggered?
Upvotes: 1
Views: 1301
Reputation: 115242
The getElementsByTagName
method returns a live HTML element collection when you removed an element from DOM it will get updated so it will skip some elements since the index of the element is changing.
To make it works to use a variable which holds the length of collection and iterate backward.
function removeLinks() {
const head = document.getElementsByTagName('head')[0];
const links = head.getElementsByClassName('link-to-remove');
let i = links.length;
while(i--) {
head.removeChild(links[i]);
}
}
Or alternately, you can use forEach as well
function removeLinks() {
const head = document.getElementsByTagName('head')[0];
const links = head.getElementsByClassName('link-to-remove');
links.forEach(el => el.remove())
}
Much simpler one using querySelectorAll
, forEach
and remove
methods.
function removeLinks() {
document.querySelectorAll('head .link-to-remove').forEach(el => el.remove())
}
Upvotes: 3
Reputation: 44078
<link>s
can be removed like any other tag. The older methods like .getElementsByClassName()
, .getElementsByTagName()
, and .getElementsByName()
are Live HTMLCollections (an array-like object) so the .length
is updated every iteration and it diminishes and prematurely ends the loops. You can achieve your objective by disabling each <link>
instead of removing them (.length
isn't touched.) or use a collection method that returns a static NodeList (an array-like object) like the more versatile .querySelectorAll()
method. Another way is to convert an array-like object into an array by Array.from()
This demo:
shows two different interfaces to handle <link>
First one uses the styleSheet
Object of the CSSStyleSheet interface
<link>s
Second HTMLLinkElement interface of the DOM using .querySelectorAll()
Click the red button to to remove the targeted <link>s
Note: A Live HTMLCollection was made so an accurate count could be made of the link.link-to-remove
in the console when the they are removed.
To properly test each example, rerun the demo and then click the other button.
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" class="link-to-remove">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" class="link-to-remove">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" class="link-to-remove">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" class="link-to-remove">
</head>
<body>
<button class='btn btn-warning'>CLICK TO DISABLE ALL STYLESHEETS</button>
<a href='#/' class='btn btn-danger'>REMOVE ALL <code>link.link-to-remove</code></a>
<script>
// Collect all stylesheets and convert to an array
let sheets = Array.from(document.styleSheets);
console.log('=========STYLESHEET QTY===========');
// Test: Check how many stylesheets there are
console.log('This page has ' + sheets.length + ' stylesheets');
console.log('=============All HREF=============');
// Test: View each stylesheet href
for (let sheet of sheets) {
console.log(sheet.href);
}
document.querySelector('button').onclick = function(e) {
// Start disabling all stylesheets beginning at the 3rd position
for (let s = 0; s < sheets.length; s++) {
if (s >= 2) {
sheets[s].disabled = true;
}
}
console.log('=============DISABLED=============');
// Test: See which stylesheets were disabled
for (let sheet of sheets) {
console.log(sheet.disabled);
}
}
let nodeList = document.querySelectorAll('.link-to-remove');
let htmlCol = document.getElementsByClassName('link-to-remove');
document.querySelector('a').onclick = function(e) {
nodeList.forEach(link => link.remove());
console.log('=========STYLESHEET QTY===========');
// Test: Check how many stylesheets there are
console.log('This page has ' + htmlCol.length + ' stylesheets with the className: .link-to-remove');
console.log('=============All CLASS=============');
// Test: View each all link.class
for (let tag of htmlCol) {
console.log(tag.className);
}
}
</script>
</body>
</html>
Upvotes: 0
Reputation: 371049
The problem is that getElementsByClassName
returns a live HTMLCollection
- if you remove an element from it during an iteration, the collection will change when you remove it. For example:
const foos = document.getElementsByClassName('foo');
console.log(foos[1].textContent);
foos[0].remove();
console.log(foos[1].textContent);
<div class="foo">a</div>
<div class="foo">b</div>
<div class="foo">c</div>
It's quite unintuitive. Either iterate backwards through the collection (eg, from length - 1
, to length - 2
, ... 0
), to ensure every element gets iterated over, or use querySelectorAll
instead, which returns a static NodeList
:
const links = head.querySelectorAll('.link-to-remove');
Another benefit to querySelectorAll
is that newer browsers support forEach
on NodeLists, so you can iterate over them directly, rather than having to use an ugly for
loop:
document.querySelectorAll('head .link-to-remove').forEach((removeMe) => {
removeMe.remove();
});
(as you can see, there's no need to select the head
if you put the head
in the query string passed to querySelectorAll
- you may also remove()
an element directly, rather than having to reference its parent and use removeChild
)
Upvotes: 6
Reputation: 1531
you have to set the stylesheet to be disabled as it keeps the css styles in memory so removing the element will not work, it can also cause it to crash in some instances if I remember correctly.
function removeLinks() {
const head = document.getElementsByTagName('head')[0];
const links = head.getElementsByClassName('link-to-remove');
for(let i = 0; i < links.length; i++) {
links[i].disabled = true;
}
}
Eg:document.styleSheets[0].disabled = true;
Jquery way => $('link[title=mystyle]')[0].disabled=true;
Upvotes: 1