Parent cannot .append() .prepend() the same created element - javascript

Interested to know if anyone can help describe the internals for the behaviour I am seeing.
Essentially, when creating a new dom element (then storing in a const) this element cannot be appened and prepended to the same parent element.
Example:
const ul = document.querySelector('ul');
const button = document.querySelector('button');
button.addEventListener('click', () => {
const li = document.createElement('li');
li.textContent = 'new li';
ul.prepend(li);
ul.append(li);
});
It seems the last call to either append or prepend, note if you call prepend last the new element is only added to start of the ul.
Digging into this it seems the cloning the node works prior to the subsequent append/prepend call.
const li = document.createElement('li');
li.textContent = 'something new to do';
ul.prepend(li);
const newLi = li.cloneNode(true);
ul.append(newLi);
However I'm interested to know the inner workings of this and why you can't seem to call against the same element? Can anyone shed any light on this as the mozilla docs don't seem to shed any light on this.
Fiddle: https://jsfiddle.net/gf7b0pom
Thanks everyone!

I think the confusion here is perhaps your understanding of what is stored created and stored in the line const li = document.createElement('li');. In this case, li doesn't contain a template that can be used over and over- when you use document.createElement() you are creating a single instance of an element of your choice. That single instance can only be in one place at a time.
As an analogy, I believe you are imagining li to be something like a rubber stamp, that can be used anywhere on a sheet of paper to make a list item. In reality, li is more like a sticker -- it can only be in one place on the paper at a time. When you run:
ul.prepend(li);
ul.append(li);
...it is like you stick your list item sticker to the front of the list, then you peel it off and stick it to the back of the list. This is why you need to call .cloneNode(true)-- it is essentially giving you a duplicate sticker to use elsewhere.

The reason is actually simple. When you create a new element, you create only one element. In your event listener, you first prepend that element then you append the same element.
ul.prepend(li);
ul.append(li);
So code is doing what you ask. It is moving the element but it is using the same element because you don't have a 2nd element.
On the other hand you want to append and prepend total of 2 elements which contains the same shape/data.
So you need 2 DOM elements for that.
If you also clone an element, you can also use it once. So if you want to append that element to multiple places, then every-time you need to clone then append.

Related

Remove all text from a specific tag

I want to clear all list items in my <body>. So on a button press, the item text from all the lists are removed. No use of IDs or anything, just if there is an <li> tag, it's innerHTML is cleared out.
I have tried a few different methods, but I cannot figure it out.
Any help is appreciated.
Basically what I was trying to do is clearly incorrect as you can see here:
function clearLI() {
document.getElementById("li").innerHTML = ""
};
You can do this quite simply by iterating over every li element and settings its innerHTML to nil.
Here's the code.
Array.from(document.getElementsByTagName('li')).forEach(el => el.innerHTML = '');
You mean
innerHTML.replace(newValue);
?
Or did you forget to hyperlink Li to body?

Add an id to a dynamically created element

I'm trying to add an id to an element that I create dynamically using javascripts' document.createElement() method. Basically I want to create an iframe in my html document and at the same time give that newly created element an id.
Here's my code so far. I've figured out how to put the element in the DOM and all that, i just need the id.
function build(content){
var newIframe = document.createElement("iframe");
var newContent = document.createTextNode("Hello World!");
newIframe.appendChild(newContent);
var element = document.getElementById("container");
document.body.insertBefore(newIframe, element);
document.getElementsByTagName("iframe").id = "active";
};
As you can probably see, I have tried to give it an id at the very end. Problem is, it doesn't work.
So if anyone has any idea of what is wrong here, or an alternative way of doing what I want to do, please feel free to express yourself. Many thanks!
Just add an attribute (id is an attribute) to that element directly, like this:
var newIframe = document.createElement("iframe");
newIframe.id = 'active';
... although it looks quite strange to have id equal to active (too generic for a unique identifier).
Your current approach doesn't work because document.getElementsByTagName("iframe") returns a collection of elements - NodeList or HTMLCollection (it's browser-dependant). While you can assign a value to its id property, it won't do what you mean to. To make it work, you can adjust it this way:
document.getElementsByTagName("iframe")[0].id = "active";
... but, as shown above, there's a better way.
newIFrame.setAttribute("id", "something");

Fade In Document Fragment

I'm using a technique from this answer for fading in an appended element with jQuery:
$(html).hide().appendTo("#mycontent").fadeIn(1000);
And trying to apply it to a document fragment:
var docfrag = document.createDocumentFragment(),
div = document.createElement('div');
div.textContent = 'Fade This In';
docfrag.appendChild(div);
But when I try to fade in the fragment:
$(docfrag).hide().appendTo('#container').fadeIn();
I get this error from jQuery:
Uncaught TypeError: Cannot set property 'cur' of undefined
Does anyone know how to fade in the document fragment?
--
Trying to fade in appended list items? See answer below.
$('docfrag') is a DocumentFragment, which has no hide().
Here is the revised code in jsFiddle: link
Reason:
DocumentFragments are DOM Nodes. They are in memory when created and never part of the main DOM tree. Which means jQuery selector will not return as expected a DOM element.
I didn't specify this in my question, but the document fragment I'm appending is a series of list items.
Since the list is already rendered with other items, I didn't have the option of fading in a container div the way Blaise and adeneo do in their fiddles.
Fading in the container <ul> would mean fading in the entire list, including the items already there. And I couldn't add another container for just the new list items without breaking the markup or making it really messy.
My solution is a bit hack-ish but gets the job done. This is for a Backbone.js application, so the code here is simplified (and ugly) to get the basic point across without all the Backbone stuff.
var docfrag = document.createDocumentFragment(),
item1 = document.createElement('li'),
item2 = document.createElement('li');
item1.textContent = 'New List Item 1';
item2.textContent = 'New List Item 2';
docfrag.appendChild(item1);
docfrag.appendChild(item2);
$item1 = $(item1);
$item2 = $(item2);
$item1.addClass('hidden').hide();
$item2.addClass('hidden').hide();
$('#button').on('click', function() {
$(docfrag).appendTo('#list');
$('#list').find('.hidden').fadeIn();
});
Once the new list items are accessible to jQuery (in my case, after running render() on their Backbone views), I hide them and add a class hook for fading them in later.

Javascript Sections

I have a website I want to take that always has the same section with the same id with all the content I want to display. I'm not very amazing at javascript and I'm wondering how I could remove everything but a specific section.
Would the best approach be to just do a loop that goes through all the elements in the DOM and remove everything but the section with the id I want to keep? If I go that approach how do I keep it from removing all the elements inside that section?
Perhaps another way to do this more efficiently would be:
document.body.innerHTML = document.getElementById( 'saveContentId' ).innerHTML
Removing one node includes all its children, so you won't need to loop over all elements in the whole document. I see two possibilities:
get the section, remove all its siblings in the current parent, and then walk up the DOM tree until document.body, while removing all siblings.
get the section and detach it from the document. Then clear document.body and re-attach the section there
The first solution seems cleaner to me, so here some sample code:
function removeEverythingBut(el) {
while (el != document.body) {
var par = el.parentNode;
for (var i=par.childNodes.length-1; i>=0; i--)
if (par.childNodes[i] != el)
par.removeChild(par.childNodes[i]);
el = par;
}
}
// usage:
removeEverythingBut(document.getElementById("my-section"));
you can save only the element you want and delete all other elements. Also I recommend using Jquery

prototype cleanWhitespace() is not working for inserted items

I'm adding a new element into OL list using prototype. In order to use .firstChild property I'm using a cleanWhitespace() function before doing anything. This is working quite nice and if I'm tracing innerHTML of my OL after it is just a plain string as expected.
Then I'm adding a new LI item and it is added with whitespaces. Applying cleanWhitespace() function after adding new LI is not making any effect. Any ideas why?
Here is my bit of code:
$$('div.pagination > ol').each(function(item){
item.cleanWhitespace(); // Working
if(!item.firstChild.childElementCount) {
var prevLi = document.createElement('li');
prevLi.update('«');
prevLi.addClassName('disabled');
item.insertBefore(prevLi, item.firstChild);
}else{
item.firstChild.children[0].update('«');
}
item.cleanWhitespace(); // Not working
});
Thanks in advance.
I'm not quite sure I understand why you're using cleanWhitespace here, but if it's only to use firstChild to not hit any text nodes, I'd definitely forego all that and just use Element#firstDescendant as it skips text and comment nodes. All of Prototype's DOM traversal methods (descendants, immediateDescendants, etc) skip the garbage you don't want.

Categories