Appending elements to children of HTMLcollection - javascript

I am trying to loop through an HTMLcollection and append a newly created element to respective children. There are 3 conditions I am looking out for if the children have 1 of 3 classes:
type-freebie
type-hack
type-post
I have been able to set most of it up and successfully attach the element I need to each distinct group, however, when two or more of any distinct type appear, only one gets appended and the other one does not. I have tried to figure out what the problem and I think it has to do with something with HTMLcollections not working like arrays but I can't seem to be able to place my finger on it.
I have pasted the code snippets below of what I have so far and the HTML I am trying to manipulate in the DOM after it.
Would appreciate any insights I might be missing.
const card_Group = jQuery('#latest-cst-query > .elementor-widget-container > .elementor-posts');
const cf_div = document.createElement("a");
const ch_p = document.createElement("a");
const cp_a = document.createElement("a");
for (cards of card_Group) {
let cardChild = cards.children;
for (card of cardChild){
if (card.classList.contains("type-freebie")){
cf_div.classList.add("freebie_tax", "freebie_fcol");
cf_div.innerText = "Freebie";
card.firstElementChild.append(cf_div);
} if (card.classList.contains("type-hack")){
ch_p.classList.add("freebie_tax", "freebie_hcol");
ch_p.innerText = "Hack";
card.firstElementChild.append(ch_p);
} if (card.classList.contains("type-post")) {
cp_a.classList.add("freebie_tax");
cp_a.innerText = "Blog";
card.firstElementChild.append(cp_a);
}
}
}

The issue here is that you create only one element per group and when appended second time it actually being moved instead.
So what you need to do is create new element before it's appended.
for (card of cardChild){
if (card.classList.contains("type-freebie")){
let cf_div = document.createElement("a");
cf_div.classList.add("freebie_tax", "freebie_fcol");
cf_div.innerText = "Freebie";
card.firstElementChild.append(cf_div);
}
You can even clone it too:
const cf_div = document.createElement("a");
cf_div.classList.add("freebie_tax", "freebie_fcol");
cf_div.innerText = "Freebie";
for (card of cardChild){
if (card.classList.contains("type-freebie")){
card.firstElementChild.append(cf_div.cloneNode(true));
}

Related

How to loop through object and if span innerText matches the key, then setAttribute value of that key?

I'm trying to create a chrome extension, that will loop through a certain span innerText and if that value matches it will change that text to hyperlinked text with a URL connected to that specific word.
On the web page there are these tags (chat,flamingo,sample ticket, test, etc.) there is a <span> element within a span element with a class "badge-tag"
What I plan is to have an object with numerous entries, key will represent the text of tag(<span>) and the value will be the URL I want to convert this to.
The html will look something like this so I have to grab the value of document.getElementsByClassName("badge-tag").innerText.
Sample html
<span class="badge-tag"><span>facebook</span></span>
<span class="badge-tag"><span>youtube</span></span>
<span class="badge-tag"><span>twitter</span></span>
<span class="badge-tag"><span>bing</span></span>
I created this JS:
if(typeof listitems === 'undefined'){
const init = function(){
const listitems = document.getElementsByClassName("badge-tag");
const tagMap = {
"facebook":"https://google.com",
"youtube":"https://youtube.com",
"test/obj":"https://docs.gorgias.com",
"instagram/dm":"https://instagram.com"
};
// console.log(tagMap['facebook'])
if(text in tagMap){
console.log("ok")
}
Object.values(listitems).forEach(item => {
const text = item.firstChild;
const link = document.createElement('a');
link.setAttribute("href", tagMap); //test link.setAttribute("href", "https://google.com");
link.setAttribute("target", "_blank");
item.appendChild(link);
link.appendChild(text);
// document.querySelectorAll('.badge-tag a')
// .forEach(function(elem){
// elem.setAttribute('target','_blank');
// })
}
)
}
init();
}
What this code does is converts all the <span> with class "badge-tag" to hyperlinked, I need a way to connect the object tagMap with it so that only the if text is equal to object key, it will add it's respective value.
I understand that I'm missing a good chunk here, as I need For Loop, and probably something else to validate but I'm kinda stuck, any help would be appreciated even if it's just a link to an article.
This is the JsFiddle I was messing with https://jsfiddle.net/94yd2h3g/31/
Thanks!
I made only a few minor modifications to your code:
/* if(text in tagMap){
console.log("ok")
console.log(tagMap[text])
} */
if(typeof listitems === 'undefined'){
const init = function(){
const listitems = document.getElementsByClassName("badge-tag");
const tagMap = {
"facebook":"https://google.com",
"youtube":"https://youtube.com",
"test/obj":"https://docs.gorgias.com",
"instagram/dm":"https://instagram.com"
};
// console.log(tagMap['facebook'])
text = "facebook"
if(text in tagMap){
console.log("ok")
}
Object.values(listitems).forEach(item => {
const text = item.textContent;
if(text in tagMap) {
const link = document.createElement('a');
link.setAttribute("href", tagMap[text]);
link.setAttribute("target", "_blank");
while(item.firstChild)
link.appendChild(item.firstChild);
item.appendChild(link);
}
// document.querySelectorAll('.badge-tag a')
// .forEach(function(elem){
// elem.setAttribute('target','_blank');
// })
}
)
}
init();
}
The text you're looking for is best achieved with Node.textContent. I also added a loop to run through all children and add those, which should make the implementation a bit more flexible. Briefly, you can have a situation where an element's text can be made up of more than one text node and looping through .firstChild accounts for that.
Of course, there are multiple ways to skin a cat. You could create a new normalised text node and destroy the old one, use innerHTML, etc., but this is what seemed simplest to me and required the fewest changes.

Clickable areas arent extending to the text

Im making a note taking application and the idea here is im trying to make a selection function to transfer my notes from the side list to the editor on the right side of the screen when clicked. So I have the list items all being created dynamically while the ul is created static.
The trouble im facing is how hard it is to click the list items. I have it console.log the work "click' whenever it registers and im not sure why its not working like its suppose it it looks like it only registers when I click the margins of the children of the li and not the h1 or p tags. Also if I click to the side, it makes my ul clickable which is not good as it would just throw everything in the editor.
I have it so when the li tags are created. It dynamically creates then via JS, adds li,h1,p,p. Appends the h1,p,p tags to the li then the li to the container.
Below is an example of my code:
//This is just to show you what happens when you click
//the submit button and how my divs are being dynamically created.
//I have them being created automatically from
//localstorage on load as well in this same fashion.
submitbutton.addEventListener('click', () => {
//Creating local variables for new notes
let NewNoteContainer = document.createElement('li')
let NewNoteTitle = document.createElement('h1')
let NewNoteDueDate = document.createElement('p')
let NewNoteContent = document.createElement('p')
let RemoveButton = document.createElement('button')
//Adds values of the input boxes to the newley created Elements
NewNoteContainer.id = NoteID
NewNoteContainer.className = 'notes-list-item'
NewNoteTitle.innerHTML = title.value
NewNoteDueDate.innerHTML = dueDate.value
NewNoteContent.innerHTML = content.value
//This appends the newly created elements to the container and then
//Appends the container to its div "note-list-container"
NewNoteContainer.append(NewNoteTitle, NewNoteDueDate,NewNoteContent)
NoteListContainer.append(NewNoteContainer)
//Creates an array, makes it a JSON string. Then stores it in localStorage
let StorageArray = [NoteID,title.value,dueDate.value,content.value]
localStorage.setItem('Note' + NoteID, JSON.stringify(StorageArray))
NoteID += 1
})
//This is the debug function im using to try and diag the click issue.
NoteListContainer.addEventListener('click', (a) => {
if(a.target.parentNode.className == 'notes-list-container') {
console.log('click')
}
})

How to remove all tags from the head section with a same attribute and tag name

I am making a JS function, which will change the icon of the document. Now, I know that this sounds simple, but it is not. I am trying to make a Library, so I need a function that can change the default icon to the desired icon and vice versa. It works fine if I try to change the default icon to the desired icon, but if I try to change the icon to the default one(the one with a gray globe), it does not work.
icon = function(dir){
if(typeof(dir)==="string"){
var elem = doc.createElement('link');
elem.rel = "icon";
elem.href = dir;
doc.head.append(elem);
}else if(typeof(dir)==="boolean"&&dir===false){
var toBeDel = [];
var currElems = document.querySelectorAll('link');
var i = 0; var f = 0;
while(currElems.length>=0){
if(currElems[i].rel=="icon"){
toBeDel.push(currElems[i]);
currElems.length--;
i++;
}
}
while(toBeDel.length>=0){
doc.head.removeChild(toBeDel[f]);
toBeDel.length--;
f++;
}
var elem = doc.createElement('link');
elem.rel = "icon";
elem.href = "/default/noimage.png";
doc.head.append(elem);
window.setTimeout(function(){
console.clear();
var len = efun.logs.length;
var sen;
var i = 0;
while(len>=0){
sen = efun.logs[i];
efun.gh = sen[0];
console.defaultLog(sen[0]);
len--;
i++;
}
},10);
}
else{
efun.cons("Error in icon() function, the parameter you have entered is not a string directory.");
}
}
Note: efun object is the main library object, efun.cons() is a shortcut to console.log(), efun.logs is an array which contains all console.logs() done. Note that I called the console.clear() function to remove the error which comes when I try to set the default icon since it does not exist. console.deafultLog() is a function, which console.logs() with it not being pushed into the efun.logs array. But the error is that I want to remove all the <link rel="icon" href="<!---directory here-->" elements from the head tag. There can be many elements with icons, so I want to remove all of them. For this, I used document.querySelectorAll() in an array, and did several while loops. But still, even though a new link element gets appended into the <head> tag, the previous ones which I want to remove remains there, so how can I remove all tags with <link> tag and rel="icon" attribute. Is there any fix to this?
I think you may have overcomplicated this.
You can delete all existing <link> tags with just one line
document.querySelectorAll('link[rel="icon"]').forEach(link => link.remove());
To revert back to the "globe" icon, you just need to change the url to something that doesnt exist.
var link = document.createElement('link');
link.rel = 'icon';
link.href = 'https://fake.url.com/';
document.getElementsByTagName('head')[0].appendChild(link);
If you want to handle errors better, you can use try catch blocks.

Javascript code leaking memory to DOM nodes - where is the Javascript reference

I have the following JavaScript code below running on my Chrome Browser. It inserts 2 buttons, one to insert a select Element with 100,000 options if the list does not already exist, and another button which deletes the list if it does exist.
<!doctype html>
<html>
<head>
</head>
<body>
<script type="text/javascript">
insertButtons()
function insertSelectList(){
let selectList = document.querySelector("#selectList")
if(selectList!=null)return;
selectList = document.createElement("select")
selectList.setAttribute("id", "selectList")
for(let i=0; i<100000; i++){
let option = document.createElement("option")
option.setAttribute("value", i)
option.innerText = i
selectList.appendChild(option)
}
document.body.appendChild(selectList)
}
function removeSelectList(){
let selectList = document.querySelector("#selectList")
if(selectList==null)return;
selectList.remove()
}
function insertButtons(){
let insertListBtn = document.createElement("button")
let removeListBtn = document.createElement("button")
insertListBtn.innerText = "Insert List"
removeListBtn.innerText = "Remove List"
insertListBtn.addEventListener("click", insertSelectList)
removeListBtn.addEventListener("click", removeSelectList)
document.body.appendChild(insertListBtn)
document.body.appendChild(removeListBtn)
}
</script>
</body>
</html>
My script seems to be leaking memory as per the chrome DEV tools memory tab's memory snapshot functionality. Using the 2 buttons, I create and delete a Select list a few times, and after doing this, the memory usage of my webpage shoots up (see image linked below for reference). The select lists that I have created and subsequently deleted seem to be hanging out in memory. When we view the second snapshot in comparison with the first, there is an increase in the option and select elements.
As per my understanding when an HTML node is removed from the DOM tree and there is no javascript reference to the node, it should be freed up by the garbage collector. Can someone please tell me why these select nodes and their option children are sticking around in memory? I dont see a javascript reference to the select nodes.
Chrome Dev Tools memory snapshots,
I don't know the reason of this behaviour (in fact, there are some related Chromium bugs open - you can find them easily if you search). But let me show you what I found.
One of the conditions for your detached elements be garbage collected is that there should be no variables referencing them. Example:
<script type="text/javascript">
// Note these variables outside the function!
// Your DOM elements will be referenced even tho they're removed
let div;
let span;
const append = () => {
div = document.createElement('div');
span = document.createElement('span');
div.appendChild(span);
span.remove()
div.remove();
}
document.addEventListener('click', append);
</script>
As a result, your detached elements will be still in memory:
So let's move them inside our function:
<script type="text/javascript">
const append = () => {
let div = document.createElement('div');
let span = document.createElement('span');
div.appendChild(span);
span.remove()
div.remove();
}
document.addEventListener('click', append);
</script>
Awesome! Function executed, internal variables not exist anymore, detached elements are not in memory:
However, I can find some weird behaviour if I replace div and span elements with select and option:
<script type="text/javascript">
const append = () => {
let select = document.createElement('select');
let option = document.createElement('option');
select.appendChild(option);
option.remove()
select.remove();
}
document.addEventListener('click', append);
</script>
Memory contains quite a lot of detached elements, including multiple Detached InternalNode, Detached HTMLSlotElement, Detached HTMLDivElement etc:
Honestly, it looks like a bug for me. Related to internal select tag implementation. But I might be wrong and missing something.
Just a couple of more observations:
Without a child option it works as expected:
const append = () => {
let select = document.createElement('select');
document.body.append(select);
select.remove();
}
If function is executed not as a listener for some event (like document click event) but executed directly instead - everything works as expected as well:
<script type="text/javascript">
const append = () => {
let select = document.createElement('select');
let option = document.createElement('option');
select.appendChild(option);
document.body.append(select);
option.remove()
select.remove();
};
append();
</script>

.onclick() only fires on the first button

When I clicked the "show comments" button, the result always only shown on the first button even though I clicked on the other button (2nd, 3rd, 4th, ... button). The content I wanted is shown correctly, but it doesn't displayed on the corresponding button.
I already tried to search the solutions over the internet, but I don't think I found the one, especially the other solutions answered with the use of jQuery. (No jQuery solutions, please)
HTML:
<div id="card-container" class="posts-card-container">
<p><strong><em>Press button "Show Posts" above (any user)</em></strong></p>
</div>
some of the script.js:
function showPosts(data) {
let cardContainer = document.getElementById('card-container').querySelector('p');
cardContainer.innerHTML = '';
for (let i = 0; i < data.length; i++) {
let cardTitle = '<h3>TITLE: '+data[i].title+'</h3>';
let cardBody = '<p><em>'+data[i].body+'</em><p>'
let btnShowComments = '<td><button id="button-show-comment" class="button-comments" postId='+data[i].id
+' onclick="loadComments('+data[i].id+')">Show Comments</button></td>';
let cardShowComments = '<div id="show-comments"><p></p></div>';
let newCard = '<div id="card-container" class="child-card-container">'+cardTitle
+cardBody+btnShowComments+cardShowComments+'</div>';
cardContainer.innerHTML += newCard;
}
}
function showComments(data) {
let commentContainer = document.getElementById("show-comments");
commentContainer.innerHTML = '<h2>Comments</h2>';
for (let i = 0; i < data.length; i++) {
let commentPoster = data[i].name+' '+'('+data[i].email+')';
let commentBody = data[i].body;
let newComment = '<p><strong>'+commentPoster+'</strong><em> commented:
"'+commentBody+'</em></p>';
commentContainer.innerHTML += newComment;
}
}
I expect the newComment is displayed on the corresponding button (e.g: if onclick() happened on the third button, newComment must be displayed under the third button), but in my code the newComment always displayed on the first button only.
By definition document.getElementById only returns one DOM element, the first one found, even if multiple with the same id are present. You should find a different way of selecting the button and you should also avoid having multiple elements with the same id as it's intended to be unique.
Edit: I realized after the fact that that's not exactly what you're asking, but you should still avoid using the same id multiple times, it causes all kinds of problems.
The problem here is that querySelector() in showPosts() will only ever give the first element in the set of elements you are looking through that fits the selector.
Which isn't what you want, so instead, we can hook up this function to an event, where we put an onclick event surrounding the div containing all buttons, and use event.target to select the button that you are using. So, something like:
document.getElementById("card-container").onclick=function showPosts(event)
{
let data=getData();//put in something like this
//no needed element checking since only buttons have onclick events
let cardContainer = event.target;
...
}
However, you will have to get data another way in order to use it in the function. Not to mention, you shouldn't be using the same ID in two differing elements. IDs are for directing the browser towards a single element.

Categories