Here's the deal - I've got a load of elements on a page, and I'm using Javascript to remove some of them (this.parentNode.removeChild(this)) which is working great. However, if I have a variable referring to this node, then remove the node, the variable does NOT lose it's value! But if I then try and perform some other actions on this element, I get errors!
Example:
var element = document.getElementById('ooolookatmeimanelement');
element.parentNode.removeChild(element);
alert(element);
I still get "[Object HTMLblahblahblah]" in the alert, rather than null or undefined - anyone got any ideas how I can check to see if the node has been removed? It's probably something really simple that I'm oblivious to!
If you remove the node, remove the references too. E.g. in your code, assign null to element:
element = null;
Or if this is not possible, you can always check the value of parentNode. It should give null if the element is not part of the DOM:
if(element.parentNode === null) {
// element is not part of the DOM
}
But this does not make much sense to me (might depend on the context though): If you removed an element, then you know that you have removed it. Why would you do any further operations on it?
You can also use document.body.contains(element); or $.contains(body, element).
Checking for the parentNode is not reliable; if what is removed is an ancestor of the element, instead of the element itself, then parentNode will still return the parent, while the element is not in the DOM anymore.
var ancestor = document.querySelector('div');
var ghost = document.querySelector('br');
ancestor.parentNode.removeChild(ancestor);
console.log(ghost.id + ' son of ' + ghost.parentNode.id);
console.log(ghost.id + (document.body.contains(ghost) || ' dont') + ' exists in DOM');
<div id="ancestorToRemove">
<p id="a bitch">
<br id="you">
</p>
</div>
.removeChild() only removes the element from the DOM, and not from memory.
Hence it still exists until it's garbage collected. However you're holding a reference to it, so that won't happen.
Another point that may save time if you're needing to run JavaScript triggered by onClick which does something to itself, you can do this:
onClick="if(!document.getElementById(this.id))return false; YOUR CODE HERE"
For me, this came up when I had a DIV with several children, one of which was a "remove this div" button (actually a DIV made to look like a button). When clicked, this 'button' would remove the DIV from the DOM, as expected, but then would call the onClick event of the DIV itself (which no longer exists). This caused problems. By adding this to my DIV's onClick, I prevented running the code which refers to the DIV after its removal.
Related
I'm writing a chrome extension, part of the functionality of which requires me to hide every html element at times with the exception of one div which I've created. (I hide everything and add the div to the current website in javascript) Because setting document.body.style.display = "none" will not allow any of the body's children to be seen, I need to add the child that I want to be seen somewhere else. (I also tried using style.visibility but for some reason that didn't hide certain HTML elements/backgrounds on certain pages.) My fix is to add the div to document.documentElement, outside of document.body. This fix actually works perfectly, but seems strange. Is there anything wrong with adding a new child to the elements? Am I doing something wrong?
EDIT: A few answers have used the children of document.body, so I thought I should note that my code has to run at document_start, and though I wait for document.body to load before executing, I can't wait for all of its children to load. Hence I can't use/store the children of document.body.
Also, I'm grateful for all the answers providing alternate solutions, they're quite useful. But out of curiosity, does anybody know if there's anything wrong with what I'm currently doing? Why is it working, if so?
The W3C specification of HTML document structure says that it consists of the <head> and <body> elements, and the <body> contains the content that's intended to be rendered. Nothing is stated about elements outside these two elements.
If it seems to work it's probably just an accident of implementation -- for instances, many implementations are forgiving of things like malformed HTML.
It's perfectly fine to append elements or text nodes directly to document.documentElement.
DOM is not HTML, it has its own specification, which - being an Object Model - is naturally quite permissive:
document.documentElement is an Element [spec]:
The document element of a document is the element whose parent is that document, if it exists, and null otherwise.
Elements are allowed to have these children [spec]:
Zero or more nodes each of which is Element, Text, ProcessingInstruction, or Comment.
Create a new DIV to hold the children of the body, and hide that.
var saveDiv = document.createElement("DIV");
saveDiv.id = "saveDiv";
saveDiv.style.display = "none";
Array.from(document.body.children).forEach(el => saveDiv.appendChild(el));
document.body.appendChild(saveDiv);
A potential solution:
const body = document.querySelector('body');
body.innerHTML = `<div class="my-div"></div><div class="content">${body.innerHTML}</div>`;
Now you have the body content all snug alongside your div, both of which you can hide/show. As pointed out below, I completely spaced that this will destroy your listeners. If you want to preserve listeners, try the following:
const body = document.querySelector('body');
const fragment = document.createDocumentFragment();
const hideBody = () => {
for (let el of [...body.children]) (fragment.append(el))
}
const showBody = () => {
for (let el of [...fragment.children]) (body.append(el))
}
Attach the appropriate one to whatever your event is. This will preserve all your listeners. If you need any functionality, DocumentFragment has the querySelector() and querySelectorAll() methods.
Another method is the modal method, where you just have a div that covers the whole page. Check out the Bootstrap modals, for example. If you initialize it with data-backdrop="static" and data-keyboard="false" then it won't disappear by clicking outside or hitting esc. The element can be selected with document.querySelector('.modal-backdrop'). Set the opacity to 1 and the background to white or whatever aesthetic you're going for.
JavaScript
function productBox(event){
event.stopPropagation();
console.log(event.target);
}
var product = document.getElementsByClassName('product');
for (var g = 0, length = product.length; g < length; g++){
console.log('here');
product[g].addEventListener('click',productBox);
}
HTML
<div class="product">
<div class="productContent">
<img src="file:///C|/Users/Jacob/Downloads/12939681_1597112303636437_733183642_n.png" />
</div>
<div class="productName">
Here
</div>
<div class="productDescription">
</div>
So the problem lies in the fact that when the product element is clicked, event.target returns the actual child element of the event listener. For example, i click a "product" and it'll return productContent, productName or productDescription as the target, when actually what i want is the "product" element and then to do a .childNodes and find the image within that.
Please note jQuery is not an option, it is 30kb of stuff i won't use as this is a static html page with barely any javascript.
I've thought perhaps,
doing a check if the element is 'product' if not, take the parent and check if it's a 'product', if not go to that parent and so on. Then find the img tag within that. But i feel like that is a long winded work around.
Any thoughts?
To get the element to which the handler is bound, you can use this within the handler.
As #DaveNewton pointed out, the event object has a .currentTarget property that can be used as well. This is nice because you can have functions that have a manually bound this using .bind() or you may be using the new arrow functions, which have a this defined by its original environment, making it impossible to get the bound element that way.
You can use parentElement property of the target.
function productBox(event){
var target = event.target;
var parent = target.parentElement;//parent of "target"
//Rest of your code
}
I asked a similar question: if you have a element.click(function(){}) and the element has two or multiple siblings. If the event trigger is attached to the parent and not the target, this post popped up and is actually irrelevant to my question so I decided to post my search here for someone else.
I used this method:
if (target.closest('.NEftune').attr('rel') != undefined){
/*do something here*/
}
The closest method in jQuery and JavaScript starts from the clicked target and then bubbles up until it finds an attribute you looking for. In my case the event was attached to an Element with the class (.NEftune) and by adding an extra attribute I could determine if I was inside the container (.NEftune) which has an image inside.
I know it's a bit late, but the simplest solution to get the actual target to which the event handler was attached to is to use the event currentTarget property.
This will avoid unnecessary further DOM check and works out of the box on all modern browsers.
Event.currentTarget on MDN
The currentTarget read-only property of the Event interface identifies the current target for the event, as the event traverses the DOM. It always refers to the element to which the event handler has been attached, as opposed to Event.target, which identifies the element on which the event occurred and which may be its descendant.
The following HTML and JavaScript is taken from parts of this jsFiddle:
http://jsfiddle.net/stephenjwatkins/2j3ZB/3/
HTML:
<p class="source">
Source
</p>
<div id="target">
<p class="dummy">
Target
</p>
</div>
<button id="transfer-button">Transfer</button>
JavaScript:
var sourceEl = $('.source');
var targetEl = $('#target');
$('#transfer-button').click(function() {
targetEl.html('<p class="dummy">Transferring...</p>');
setTimeout(function() {
// Source element will be empty on second attempt to append
targetEl.html('').append(sourceEl);
}, 750);
return false;
});
Note that the setTimeout and dummy text is there simply for a visual indicator.
As one can see, after the source element is appended and removed once from the DOM, IE (all versions) will add an empty element to the DOM upon any further appends; whereas, all other browsers will add the correct, non-empty element.
Another aspect that adds to the confusion is that the sourceEl still has element information (e.g. sourceEl.attr('class') will return "source").
I know of methods to mitigate the issue (e.g. sourceEl.clone()), but it would be nice to have a better understanding as to why IE is behaving differently to avoid any related problems in the future.
What is causing the source element to be uniquely empty in IE after once replacing the element?
First let's highlight the important parts:
(first click) Takes the source element and put it inside the target element;
(second click) Empties the target element and appends a new child (p.dummy) to it, effectively removing source from the DOM;
Empties the target element and tries to re-append source, which is no longer present in the DOM.
At first look, this wouldn't work in any browser as the source element has already been removed from the DOM. The "magic" here is JavaScript's Garbage Collector. Browsers see that sourceEl is still scoped (inside the setTimeout closure) and do not trash the referenced DOM element inside of the sourceEl jQuery object.
The issue here is not JScript (Microsft's Javascript implementation)'s Garbage Collector, but rather how JScript handles the DOM parsing when setting an element's innerHTML.
Other browsers will simply detach all childNodes (whose will be collected by GC when there are no more active references) and parse the passed html string into DOM elements appending them to the DOM. Jscript, on the other hard, will also erase the detached childNodes' innerHTML/childNodes. Check this fiddle for an illustration.
The element, in fact, does still exist in IE and is appended to the DOM:
It just has no childNodes anymore.
To prevent this kind of behavior, .clone() the element (as mentioned in the question) or .detach() it before calling .html() on its parent, if you intend to re-use the element instead of "overwriting" it.
Here's a fiddle using .detach() before the element is overwritten, works fine in all browsers.
In my mind, IE is behaving correctly, and the other browsers are magical for working.
It all stems from when you call the line:
targetEl.html('<p class="dummy">Transferring...</p>');
This removes the sourceEl element from the page. So it no longer exists. I guess other browsers are remembering the DOM object as there is still a variable referencing it. But IE recognizes this as no longer existing on the page, so it loses the reference.
As you mentioned, I would recommend cloning the object when you click. This creates a new object in JavaScript. Luckily, overwriting same variable works.
sourceEl = sourceEl.clone();
http://jsfiddle.net/AbJQE/3/
edit You can also remove any possibly existing original source objects before you insert this new one. This fixes the problem for trigger happy clickers:
setTimeout(function() {
$('.source').remove();
targetEl.html('').append(sourceEl);
}, 750);
I'm trying to detach a DOM element to append it to another DOM element. But jQuery refuses to do anything, silently.
Thing is, I can't use a string selector, because I don't know how to select this element. I've stored it in a variable when I first appended some html code to the initial parent (through "appendTo".
this.element = $(my_html_string).appendTo(some_dom_parent);
And that works fine. The code that is not working as expected, is following:
this.transferTo = function(dom_parent)
{
$(this.element).detach()
$(this.element).appendTo(dom_parent);
}
What happens is:
The element is NOT removed from wherever it is.
The element IS appended to the new parent.
Previously bind click events are triggered on both elements.
That click event appends a popup dialog to the element. It's being appended to the element in the new parent, always, regardless which one I click.
I tried some hardcoded detach like:
$('#very_specific_id').detach()
... and it works. But thing is, I don't have IDs placed around, and sounds like a very bad way to do this.
So the problem seems to rely on the fact I'm saving a jQuery DOM Element and trying to use .detach from it, instead of using a $(".query") like everyone else.
Ideas? Workarounds? Thanks!
Try changing this:
this.transferTo = function(dom_parent)
{
$(this.element).detach()
$(this.element).appendTo(dom_parent);
}
to this:
this.transferTo = function(dom_parent)
{
var $thisElement = $(this.element);
$thisElement.detach()
$thisElement.appendTo(dom_parent);
}
I am really wondering if jQuery remove function really remove elements from DOM.
First, I looked here but the answers are not convincing.
I encountered this problem when I noticed I am still able to manipulate elements on which I have called remove function.
My code:
<div id="container">
<div id="div">
This is a div
</div>
</div>
var div = $('#div');
$('#div').remove();
$('#container').append(div);
Note: My question is not how to solve this? but I want to understand what's going on here!
Actually, this code doesn't remove the #div from the dom, but if I have any data set to the #div, it will be lost. I am pretty confused now about the behaviour of remove function. Can anyone explain this please?
DEMO
I am convinced that div variable is not just a clone of the dom element, is a reference to it, because when I manipulate the div variable, (like div.html('something')) the div within the DOM get updated.
Or am I wrong?
remove() does indeed remove the element from the DOM.
However in your example, the element has been cached in memory because you assigned it to the div variable. Therefore you can still use it, and append it again, and it will still contain all the events the original did.
If what you say is right, why I loose the data bound to the div so?
If you check the source for remove() you'll see that it calls the internal cleanData function which removes the events and clears the data cache for the element. This is why you lose the information.
If you want to remove the element from the DOM, but keep the elements, use detach() instead.
Here's a fiddle to show the difference: http://jsfiddle.net/2VbmX/
had to delete the assigned variable:
delete div;