Click inside Loop issue - Remove dynamically created elements - javascript

The following code takes all the links onto the page that contains "https" and not "google.com" and turns them into iFrames. While that works, the close button that each is iFrame is supposed to be paired with does not work. When you click close, it only closes the last iFrame element on the page. I prefer to be able to do this in vanilla JavaScript, as opposed to jQuery.
total = []
var links = document.querySelectorAll("a");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.href.indexOf("https") != -1 &&
link.href.indexOf("google.com") == -1) {
var hey = (links[i].href);
console.log(link.href);
total.push(links[i].href);
var iframe = document.createElement('iframe');
iframe.src = hey;
document.body.appendChild(iframe);
var close = document.createElement('button');
document.body.appendChild(close);
close.innerHTML = "close";
close.addEventListener('click',function(){
console.log("click");
document.body.removeChild(iframe);
document.body.removeChild(close);
})
}
}

It's a Variable Hoisting issue
where the var is hoisted to the closest scope, in your case it's window (since you don't have any other parent function wrapper), and reassigned/overridden again and again inside the loop - always leading to the last iterated element.
Quickfix:
var iframe and var close should be defined as const to remain inside the scope of that for loop body:
var links = document.querySelectorAll("a");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.href.indexOf("https") != -1 && link.href.indexOf("google.com") == -1) {
const iframe = document.createElement('iframe'); // Quickfix
iframe.src = links[i].href;
iframe.id = Math.random();
document.body.appendChild(iframe);
const close = document.createElement('button'); // Quickfix
document.body.appendChild(close);
close.innerHTML = "close " + link.href;
close.addEventListener('click', function() {
document.body.removeChild(iframe);
document.body.removeChild(close);
})
}
}
The proper way
not only it's a bad habit to use var nowadays, it's also a bad practice to assign Event handlers inside a for loop. So here's a remake which removed completely the var keyword, uses some nifty reusable DOM utility functions, and at last — the NodeList.prototype.forEach() method:
// DOM utility functions:
const EL = (sel, par) => (par || document).querySelector(sel);
const ELS = (sel, par) => (par || document).querySelectorAll(sel);
const ELNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
// Task:
// Convert all http/s anchors to iframes with a
// button "Delete", wrapped inside a .figure DIV Element
ELS("a").forEach(EL_anchor => {
const href = EL_anchor.href;
if (/^https?:\/\/(?:(?:www\.)?google.com)/.test(href)) return;
const EL_figure = ELNew("div", {className: "figure"});
const EL_iframe = ELNew("iframe", {src: EL_anchor.href});
const EL_delete = ELNew("button", {type: "button", textContent: "Delete", onclick() {EL_figure.remove();}});
EL_figure.append(EL_iframe, EL_delete);
EL("body").append(EL_figure);
});
See the above's RegExp Example and desctription on Regex101.com

JS is not my primary language, but try this:
total = []
var links = document.querySelectorAll("a");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.href.indexOf("https") != -1 && link.href.indexOf("google.com") == -1) {
var hey = (links[i].href);
console.log(link.href);
total.push(links[i].href);
var iframe = document.createElement('iframe');
iframe.src = hey;
document.body.appendChild(iframe);
var close = document.createElement('button');
document.body.appendChild(close);
close.innerHTML = "close";
close.addEventListener('click',function(){
console.log("click");
document.body.removeChild(iframe);
document.body.removeChild(close);
document.body.removeChild(document.getElementById("iframe"));
});
}
}

for (var i = 0; i < links.length; i++)
Change var to let.
OR
Use forEach instead of for loop.
The reason of this is scoping. var is function-scoped. Because above loop runs inside one function, var remains common for all loop iteration. On the other side, let or const are block-scoped. Because for loop creates individual blocks, each of the blocks created by the loop will work with an individual variable. forEach also creates individual scope. All variables will have values independent from each scope

If I understand correctly you want to create an <iframe> based on the href of pre-existing <a>s. You also want a <button> for each <iframe> that closes it.
In the example below are two functions:
linksToIframes(urlFrag)
Creates a box to put <iframe>s and <button>s into. Throwing them on <body> is messy:
document.body.insertAdjacentHTML('afterBegin', `<fieldset></fieldset>`);
const box = document.querySelector('fieldset');
Given a fragment of a url, it will collect all <a> into a HTMLCollection:
const links = document.links
Convert HTMLCollection into an array and iterate through it finding any matches of href and urlFrag:
[...links].forEach(link => {
if (link.href.includes(urlFrag)) {...
Any match create the <frame> and <button> in the <fieldset> (the box):
let iF = document.createElement('iframe');
iF.src = link.href;
box.appendChild(iF);
...
closeIframe(event)
An event handler that enables any <button> to remove the <frame> before it and itself:
const clicked = e.target; // This is the tag user actually clicked
if (clicked.matches('button')) { /* <button> is the only
tag that's accepted (that's
Event Delegation) */
clicked.previousElementSibling.remove();
clicked.remove();
...
Because of event bubbling we can bind the event handler on the parent element of all of the <button>s and then delegate how they react to a click. That's far better than an event handler on each <button>.
If you stick to your OP code, .previousElementSibling.remove(); applied to each <button> and .remove() to itself should fix it.
const linksToIframes = urlFrag => {
document.body.insertAdjacentHTML('afterBegin', `<fieldset></fieldset>`);
const box = document.querySelector('fieldset');
const links = document.links;
[...links].forEach(link => {
if (link.href.includes(urlFrag)) {
let iF = document.createElement('iframe');
iF.src = link.href;
box.appendChild(iF);
let btn = document.createElement('button');
btn.textContent = 'Close';
box.appendChild(btn);
}
});
};
const closeIframe = e => {
const clicked = e.target;
if (clicked.matches('button')) {
clicked.previousElementSibling.remove();
clicked.remove();
}
}
linksToIframes('https://example.com');
document.querySelector('fieldset').onclick = closeIframe;
a,
iframe,
button {
display: block
}
iframe {
width: 95%;
max-height: 50px;
}
<a href='https://example.com'>EXAMPLE</a>
<a href='https://stackoverflow'>SO</a>
<a href='https://example.com'>EXAMPLE</a>
<a href='https://example.com'>EXAMPLE</a>

Related

Javascript addEventListener not working properly

So i'm having trouble with the event so, normally i try to create an event if we have more than 1 image in the array so i can mouseenter and display another one but currently i don't know why but when we mouseenter the preview (image) it giving the latest result from the array while we still inside the loop ?
var product_type = "";
for(let i = 0; i < this.products_list.length; i++){
var row = this.products_list[i], self = this;
if(row.product_type != product_type){
product_type = row.product_type;
var sub_title = document.createElement("h2"),
separator = document.createElement("hr"),
display_list = document.createElement("ul");
sub_title.id = product_type;
sub_title.innerHTML = localeString.get(product_type);
display_list.id = product_type+"-list";
this.catalog.appendChild(sub_title);
this.catalog.appendChild(separator);
this.catalog.appendChild(display_list);
}
var product = document.createElement("li"),
preview = document.createElement("img"),
container = document.createElement("div");
var array_images = row.product_images.split(",");
preview.src = this.assets+product_type+"/"+array_images[0]+".jpg";
preview.alt = product_type+"#"+row.product_id;
container.appendChild(preview);
product.appendChild(container);
preview.addEventListener("mouseenter", event => {
console.log(array_images);
//here, giving the latest element from the array and not the current selected.
});
display_list.appendChild(product);
}
If your are trying to opt for mouseover event then you should use mouseover in the place of click event. Currently its not working because the event which you called is a click event.

Rendering Info Cards and modal box with forLoop only outs the last result in one of the fields

I am practising vainilla JS and DOM manipulation. I am rendering a group of cards populated with data from an Object (that I fetch from an API, but this happens also if i do it in local).
I get to render the cards,all good, and create a button in each of them, that triggers a Modal box made with Bootstrap, with some additional information about that item.
The problem I have is that when I click on the button, the modal box only displays the information from the last element of the Object.
The function i wrote is as follows :
function createCards(data) {
let cardContainer = document.getElementById('cardContainer');
let modalTitle = document.getElementById('modalTitle');
let modalBody= document.getElementById('modalBody');
for (let i = 0; i < data.length; i++) {
let divCard = document.createElement('div');
divCard.setAttribute('class', 'card');
let imgFish = document.createElement('img');
imgFish.setAttribute('class', 'card-img-top');
let imgUrl = data[i]['Species Illustration Photo'].src
// console.log(imgUrl)
imgFish.setAttribute('src', imgUrl);
let divCardBody = document.createElement('div');
divCardBody.setAttribute('class', 'card-body');
let hCardTitle = document.createElement('h5')
hCardTitle.innerHTML = data[i]['Species Name'];
let pCardText = document.createElement('p');
pCardText.setAttribute('class', 'card-text');
pCardText.innerHTML = 'Kcal = ' + data[i].Calories
let aInfoButton = document.createElement('a');
aInfoButton.setAttribute('class', 'btn btn-primary');
aInfoButton.setAttribute('href', '#');
aInfoButton.setAttribute('data-toggle', 'modal');
aInfoButton.setAttribute('data-target', '#myModal');
aInfoButton.innerHTML = 'Nutrional Info'
cardContainer.appendChild(divCard);
divCard.appendChild(imgFish);
divCard.appendChild(divCardBody);
divCardBody.appendChild(hCardTitle);
divCardBody.appendChild(pCardText);
divCardBody.appendChild(aInfoButton);
//fill in Modal box
aInfoButton.onclick = function () {
createModal(data);
}
}
console.log("createCards() run")
}
and then, the function to populate the modal box :
function createModal(data) {
let modalTitle = document.getElementById('modalTitle');
let modalBody= document.getElementById('modalBody');
for (let i = 0; i < data.length; i++) {
modalTitle.innerHTML = data[i]['Species Name'];
modalBody.innerHTML = data[i]['Cholesterol'];
}
}
I tried also wrapping the creation of those two innerHTML from the modal box inside a different function, thinking it was a problem of time needed to render each item, but it seems that the problem is different, and I can't see which one.
It looks like, at the bottom of your for-loop, you are overwriting the innerHTML every time.
modalTitle.innerHTML = data[i]['Species Name'];
modalBody.innerHTML = data[i]['Cholesterol'];
So when you exit the for loop, what ever iteration of data is last is the one that will be in the modal.
2 ways to go about fixing:
1.) create a modal specific to each card.
2.) add an eventListener to the button that will populate the correct info onclick

How to get the img id for dynamically building IMG tags based on click like Share or Like or Comment

Good Evening Everyone.
Background: I am getting list of images from a Mongo Database and then I am calling ajax once to load those data in to particular div.
Here I am building those img tags dynamically and then appending it to a div.
Now I am trying to get the img id based on user operation, lets say clicks on 'share button' for a particular img, then I have to get the image id, and then have to look search the DB with that image id.
My code after the ajax call is:
function showImages(imageList) {
for ( var i = 0, len = imageList.length; i < len; i++) {
var elem = document.createElement("img");
elem.src = 'getImg/' + imageList[i][0] + '/' + imageList[i][1];
elem.id = imageList[i][2];
alert(elem.id);
elem.height = '100';
elem.width = '100';
elem.alt = 'SPF HYD';
/* $("a[id=shareImage]").click(function(){
var qwerty = $("img", $(this).parent()).attr("id");
alert('image id is after anchor by click...'+qwerty);
}); */
var image = document.getElementById("imageLoad");
image.appendChild(elem);
}
}
Could any one help me to get the image id onclick or any button trigger?
I threw a quick demo together to demonstrate what I meant. It's made possible using jQuery event Delagation
function showImages(imageList) {
for ( var i = 0, len = imageList.length; i < len; i++) {
var elem = document.createElement("img");
elem.src = 'getImg/' + imageList[i][0] + '/' + imageList[i][1];
elem.id = imageList[i][2];
console.log(elem.id);
elem.height = '100';
elem.width = '100';
elem.alt = 'SPF HYD';
var image = document.getElementById("imageLoad");
image.appendChild(elem);
}
}
//The event handler is registered on the document object - the second argument here is the delegate, <img>
$(document).on("click", "img", function(e) {
alert($(this).attr("id"));
});
var imageList = [[0, 1, 2], [3, 4, 5]]; //These values are merely for testing
showImages(imageList);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="imageLoad"></div>
Using Event Delegation is necessary here because your img tags are being dynamically generated, plus it's a lot cleaner to register one event handler for all img tags, rather than an event handler for each
Hope this helps
Use addEventListener function to add an Event Listener to your dynamically created element.
var div = document.getElementById("div");
var imgShare = document.createElement("img");
imgShare.src = "http://icons.iconarchive.com/icons/graphicloads/100-flat-2/128/share-2-icon.png";
imgShare.id = "post002";
imgShare.addEventListener("click", share);
div.appendChild(imgShare);
var imgLike = document.createElement("img");
imgLike.src = "http://www.brandsoftheworld.com/sites/default/files/styles/logo-thumbnail/public/102011/like_icon.png?itok=nkurUMlZ";
imgLike.id = "post001";
imgLike.addEventListener("click", like);
div.appendChild(imgLike);
function share(e){
alert("Share id:" + e.currentTarget.id);
}
function like(e){
alert("like id:" + e.currentTarget.id);
}
<div id="div"></div>
JSFiddle Link...

tag onclick being processed twice

I want clicking on an "expando" to toggle between its states: expanded and collapsed.
I'm still pretty new to DOM/JS, so my style here is probably awful; If you have any style guidelines let me know, but for right now I want to get the code working. I've tried a few different ways, like setting the expand or collapse behavior in dom's onclick (and changing it in the expand and collapse functions), but if I do that, then for some reason clicking doesn't trigger a collapse, but it will trigger an expand.
The problem with the code below is that I can expand an expando, but when I click on it, it also triggers the collapse, so it expands and then immediately collapses back.
var expandos = document.getElementsByTagName("expando");
var uid = 0;
for(var i=0; i<expandos.length; ++i) {
var dom = expandos[i];
dom.id = "expando_"+uid++;
var iframe = document.createElement("iframe");
iframe.src = dom.innerHTML;
iframe.name = dom.id +".big";
iframe.id = iframe.name;
iframe.scrolling = "no";
iframe.style.display = "inline";
iframe.onclick = collapse(dom);
var p = document.createElement("p");
var text = document.createTextNode(dom.innerHTML);
p.id = dom.id+".small";
p.style.display = "inline";
p.appendChild(text);
p.onclick = expand(dom);
dom.innerHTML = "";
/* We have to clear the innerHTML to prevent the original text from
showing up in addition to the text added by p.
*/
dom.appendChild(iframe);
dom.appendChild(p);
/* We have to append iframe and p **after** we clear innerHTML
because otherwise clearing innerHTML will clear the appended
children.
*/
function expand(dom) {
return function() {
alert("Expanding "+dom.id);
var iframe = document.getElementById(dom.id+".big");
var p = document.getElementById(dom.id+".small");
p.style.display = "none";
iframe.style.display = "initial";
dom.onclick = collapse(dom);
}
}
function collapse(dom) {
return function() {
alert("Collapsing "+dom.id);
var iframe = document.getElementById(dom.id+".big");
var p = document.getElementById(dom.id+".small");
p.style.display = "initial";
iframe.style.display = "none";
dom.onclick = expand(dom);
}
}
collapse(dom)();
}
The sample HTML I'm testing on:
<body>
<expando>The quick brown</expando> fox jumps over <expando>the lazy dog</expando>.
<script src="loadExpandos.js"></script>
</body>
In the same directory, I have files named "The quick brown" and "the lazy dog", and they expand properly.
A quick fix for to get the basic functionality you want is to combine your expand and collapse into a single function and have an if/else block that checks the state. Not 100% on what caused your original issue, but I'd guess it has something to do with your onClick events not being cleared.
function clickHandler(dom) {
return function() {
var iframe = document.getElementById(dom.id+".big");
var p = document.getElementById(dom.id+".small");
if(p.style.display === "initial"){
p.style.display = "none";
iframe.style.display = "initial";
} else {
p.style.display = "initial";
iframe.style.display = "none";
}

Javascript failing because the dom has been altered?

These two javascript functions work perfectly on unaltered dom elements. However the delete_route function fails when asked to delete elements appended to the dom via the second function. For clarity, I am only looking at elements where parts[0] is always option - it is created by spliting the a > id on the "_".
Why is Javascript apparently seeing this difference between "native" dom objects and inserted objects?
//handle delete events
function delete_route (parts) {
if (parts[0] == "field") {
var select_container = "container_"+parts[2];
var getContainer = document.getElementById(select_container);
getContainer.parentNode.removeChild(getContainer);
} else if (parts[0] == "option") {
var optionId = parts[0]+"_"+parts[2]+"_"+parts[3];
var getOption = document.getElementById(optionId);
getOption.parentNode.removeChild(getOption);
}
}
//handle new events
function new_route (parts) {
var highest_number = -1;
if (parts[0] == "field") {
} else if (parts[0] == "option") {
var selectContainer = "container_"+parts[2];
var thisContainer = document.getElementById(selectContainer);
//get last option id (for new object tagging)
var optionList = thisContainer.getElementsByTagName("input");
var optionListLength = optionList.length -2;
//more accurate new node placement than last option which didn't work correctly anyway
lastChild = "options_wrapper_"+parts[2];
var lastChildNode = document.getElementById(lastChild);
//generate option
var labelNode = document.createElement ("label");
var inputNode = document.createElement ("input");
var linkNode = document.createElement ("a");
var breakNode = document.createElement ("br");
inputNode.setAttribute("type", "text");
var inputNodeId = parts[0]+"_"+parts[2]+"_"+optionListLength;
inputNode.setAttribute("id", inputNodeId);
inputNode.setAttribute("name", inputNodeId);
inputNode.setAttribute("value", "Undefined");
labelNode.setAttribute ("for", inputNodeId);
var labelNodeText = document.createTextNode ("Option Value");
linkNode.setAttribute("href", "#");
var linkId = parts[0]+"_delete_"+parts[2]+"_"+optionListLength;
linkNode.setAttribute("id", linkId);
var linkNodeText = document.createTextNode ("Delete option");
lastChildNode.appendChild (labelNode);
labelNode.appendChild (labelNodeText);
lastChildNode.appendChild (inputNode);
lastChildNode.appendChild (linkNode);
linkNode.appendChild (linkNodeText);
lastChildNode.appendChild (breakNode);
}
}
HTML this applies to (I have gone though some effort with the creating part - options that were inserted by javascript are exactly indentical to "native" page elements):
<div id="options_wrapper_7">
<label for="option_7_0">Option Value</label><input type=text id="option_7_0" name="option_7_0" value="Red"> <a id="option_delete_7_0" href="#">Delete option</a><br>
<label for="option_7_1">Option Value</label><input type=text id="option_7_1" name="option_7_1" value="Green"><a id="option_delete_7_1" href="#">Delete option</a><br>
<label for="option_7_2">Option Value</label><input type=text id="option_7_2" name="option_7_2" value="Blue"><a id="option_delete_7_2" href="#">Delete option</a><br>
</div>
Based on the code from your previous questions, you're assigning event handlers at window.onload by calling the clickDetection() function.
I assume when you've created new elements, you haven't bothered to give those new elements the same event handlers that your initial clickDetection() does.
If that's the case, you'll need to be sure that those new elements get handlers that can respond to clicks.
// make a separate reference to the handler so we can use it
// for elements that are created later.
function clickHandler() {
clickRoute(this);
return false
};
function clickDetection() {
var canvas = document.getElementById("content");
var dumbLinks = canvas.getElementsByTagName("a");
for (var i = 0; i < dumbLinks.length; i++) {
// Assign the "clickHandler" when the page loads
dumbLinks[i].onclick = clickHandler;
}
}
Then in your new_route function, manually assign clickHandler to the new <a> element.
function new_route (parts) {
var highest_number = -1;
if (parts[0] == "field") {
} else if (parts[0] == "option") {
var selectContainer = "container_"+parts[2];
var thisContainer = document.getElementById(selectContainer);
//get last option id (for new object tagging)
var optionList = thisContainer.getElementsByTagName("input");
var optionListLength = optionList.length -2;
//more accurate new node placement than last option which didn't work correctly anyway
lastChild = "options_wrapper_"+parts[2];
var lastChildNode = document.getElementById(lastChild);
//generate option
var labelNode = document.createElement ("label");
var inputNode = document.createElement ("input");
var linkNode = document.createElement ("a");
var breakNode = document.createElement ("br");
// ********RIGHT HERE*********
// Assign the handler to the new "linkNode" element
linkNode.onclick = clickHandler;
// ...and so on with the rest of the code...
}

Categories

Resources