Toggle appendChild if doesnt exist and removeChild if exist - javascript

I want to have a button which toggles element in the document.
I don't want to use class display: none if/else statement. Instead, I want to appendChild if it doesn't exist and if it exists, then I want to removeChild.
There is an idea of what I want to achieve, but I have some problem there. The element is shown, but on next click, it is not removed, instead, I get multiple copies of it. (I think so). Please, no jQuery. Vanilla JavaScript only. Don't know is it important, but my-element is HTML <template>.
<body>
<button id="my-button">Toggle</button>
<template id="my-element">
<div>
Some content
</div>
</template>
<script>
let element = document.getElementById('my-element');
let content = element.content;
function toggle () {
if (document.body.contains(content)) {
document.body.removeChild(content);
} else {
document.body.appendChild(content);
}
}
let button = document.getElementById('my-button');
button.addEventListener('click', toggle, false);
</script>
</body>

You should understand that template exists outside of the loaded DOM, so no matter where you physically locate it in the code really won't make any difference later. Also, understand that when you want to access content of a template, you use .content, but after that content is injected into the DOM, it's not template.content anymore, it's part of the DOM.
So, you can't search the document for template .content because, after it gets inserted, it won't be template content in your document, it will be actual DOM content. You'll need some way of identifying it and a class is the simplest way.
Also, the documentation on templates says that you bring template content into the document with document.importNode, which you aren't using.
Next, always remember that .removeChild does what its name implies, it removes child elements. document.body.removeChild() can therefore only remove children of the body element, so you need to remember this. Your code may be OK for finding the imported node as a child of body, but that may not always be the case depending on where you inserted it. The code below dynamically locates the imported content's parent node and will always remove it, regardless of where it winds up being located in the DOM.
Lastly, and this is very important, although you have indicated that you don't want to hide/show the element and would rather append it and remove it, doing so is very expensive in terms of performance. Every time you add or remove an element from the DOM, the entire DOM has to be rebuilt and the node(s) you remove don't necessarily get removed from memory even though they are not present in the DOM anymore. So, do this at your own risk. It's actually much better (from a performance standpoint) to simply hide/show content.
let element = document.getElementById('my-element');
function toggle () {
// Attempt to reference the element in the document, not the template content
var imported = document.querySelector(".imported");
// Check for the element, not the template content
if (document.body.contains(imported)) {
// Element exists, call removeChild on its parent
imported.parentNode.removeChild(imported);
} else {
// Use .importNode to bring template content in:
document.body.appendChild(document.importNode(element.content, true));
}
}
document.getElementById('my-button').addEventListener('click', toggle);
<button id="my-button">Toggle</button>
<template id="my-element">
<div class="imported">Some content</div>
</template>

Related

Best Way To Reload Element Oriented JS On Element Replace

What's the best way to re-initialize javascript without a page refresh
I'm currently trying to append an MDBootstrap <select> tag, which is not as simple as adding a child element. Instead I'm removing the element and reconstructing it with the updated data via AJAX request.
At the moment, the only possibility I see is just executing the code again after the element is recreated.
Apologies if this isn't clear enough.
What I'm attempting to try, which works, however it's not very clean:
$("#function-btn").click(function(){
$.get("api/endpoint/getprofiles", function(){}).done(function(data){
$(".select-wrapper.mdb-select.md-form").remove()
$("#charcontainer").html(data);
$('.mdb-select').materialSelect();
})
// Reinitialize other JQuery functions around the '.mdb-select' element (alot)
})
Consider the following html
<div id='wrapper'>
<div id='container'>
<span>Content</span>
</div>
</div>
If you're deleting and replacing #container you will not want to hook your selector on #container but rather your jQuery should hook onto the parent (#wrapper) first and then drill down.
Therefore it will look something like this.
$('#wrapper>#container').on('click',function(){
//do the thing
});
That way you're technically not hooking onto the element that's removed from the DOM but rather the parent (#wrapper) element even though the selector has the child.

Why does the elements inside my div container disappear?

I have a question about browser behavior when handling the elements inside of a div container. I have an Iframe that I use to make requests to the server to run PHP files, while the parent page remains dynamic. Requests made by the parent page to the child frame determines what eventually happens to certain elements on the parent page when the server returns the results to the child frame. I've tested everything, and it all works fine, except for one noticeable glitch. If someone happens to call a function on the main page that tries to read an element inside of a div container while the child frame is making a change to any element inside the same div container, the program gives a null error. When I check the elements of the div container with the debugger, all of the elements (14 in total) inside that specific div container are gone. I'm sure I can solve the problem by querying a random element first to see if it exists, and if not, put a call back function a second later once the child frame finishes with the parent page. But I'm curious as to why all elements inside that specific div container are gone at that exact moment in the first place, since the child frame only manipulates 5 of the elements inside that div, and the parent request is looking for a completely different element that is not being manipulated by the child frame. Am I correct to assume that making changes to an element inside of a div container forces a rewrite of all the elements inside that div? All other div container elements in the body of the document remain intact, just the one div that's being changed. This is not the exact code (too much to post) but an example.
// Parent
document.getElementById("heading").innerHTML = "Where did you go?";
// Child Frame
parent.document.getElementById("pic").src = "anotherpic.jpg";
<div id ="something">
<P id = "heading">blah blah blah</p>
<img id = "pic" src = "picture.jpg">
<p id = "picname">Picture Name</p>
</div>
If the Child runs first, and the Parent tries to read an element while the child is still making changes to an element inside the div, all elements disappear when I look for them in the debugger. Any insights into this behavior are appreciated. Thanks. (Hopefully, this was not a stupid question. lol)
Please mind there is no such thing as "at the same time" here. Both, the innerHTML and getElementById work synchronously.
Even if you used the innerHTML improperly (which I know isn't the case) eg. overriding the parent component - there's no "in-between state" that you'd be able to access with the functions, to notice that there are no elements for a "moment". So I'd look elsewhere to find a reason behind it.
eg. the Iframe content modifies the parent twice every time, first of which makes it empty.
Have you tried using:
target.addEventListener(type, listener [, options]);
Instead of:
getElementById
?
Looks like something is getting overwritten.

Is there anything wrong with adding an HTML element as a direct child of document.documentElement?

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.

jQuery: Detach without string selector

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

Does jQuery remove function really remove Dom elements?

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;

Categories

Resources