Javascript failing because the dom has been altered? - javascript

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...
}

Related

Click inside Loop issue - Remove dynamically created elements

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>

How to append multiple elements to a div using DocumentFragment

function JGallery() {
this.elements = this._init();
this.overlay = this.elements.overlay;
this.media_hld = this.elements.media_hld;
}
JGallery.prototype._init = function(){
var overlay = document.createElement('div');
var media_hld = document.createElement('div');
return{
'overlay': overlay,
'media_hld': media_hld
}
};
This is where I create a document fragment and using it so I can add several div to same element:
JGallery.prototype.getReference = function(holder) {
var overlay = this.overlay;
var media_hld = this.media_hld;
var that = this;
var holderChildren = holder.querySelectorAll('img');
var docfrag = document.createDocumentFragment();
holderChildren.forEach(function (e) {
e.addEventListener('click', JGallery.prototype.showMe.bind(that), false);
var media_holder = that.media_hld;
media_holder.textContent = "<img src="+e.getAttribute('src')+">";
docfrag.appendChild(media_holder);
//it only appends the last child of my array...
});
overlay.appendChild(docfrag);
};
my goal is to have something like this:
<div class="JGallery_BG">
<div class="JGallery_mediaContainer"><img src="images/thumb_video.jpg"></div>
<div class="JGallery_mediaContainer"><img src="images/thumb_video.jpg"></div>
</div>
by the way the forEach function works well, 8 or 9 times. But I'm not sure whether it adds node to docFrag on every run or not.
Another thing, I'm not insisting to use a document fragment, if there is a better way to add multiple elements to one element, I like to know about it and use it.
One of the problems is that you are constantly re-using the same media holder <div> element in every iterations.
In the code below that.media_hld is always referencing the same element.
var media_holder = that.media_hld;
media_holder.textContent = "<img src="+e.getAttribute('src')+">";
docfrag.appendChild(media_holder);
If you clone the node it should work and you also need to set the innerHTML property, not the textContent.
var media_holder = that.media_hld.cloneNode();
One other thing I did spot is that what's returned from querySelectorAll is not an array and thus doesn't have a forEach method.
You could borrow forEach from an array instance though:
[].forEach.call(holderChildren, forEachBodyFunction);
The entire thing could read:
JGallery.prototype.getReference = function(holder) {
var docfrag = document.createDocumentFragment(),
images = holder.querySelectorAll('img');
[].forEach.call(images, function (img) {
img.addEventListener('click', JGallery.prototype.showMe.bind(this), false);
var media_holder = this.media_hld.cloneNode();
media_holder.appendChild(img.cloneNode());
docfrag.appendChild(media_holder);
}.bind(this));
this.overlay.appendChild(docfrag);
};

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...

cloning html element doesnt seem to have prototype methods

I am trying to clone the contents of a modal before it disappears.
basically when the ok button is clicked it passes a clone of object to function like so
$ok.onclick = function(e){
if(params.ok)
var $cont = $content.cloneNode(true);
params.ok($cont);
closeSlert($slert);
}
however later I am trying to use getElementById() but it returns that this is not a function
I thought that Element.clone clones the prototype as well.
just incase someone thinks to suggest $content.cloneNode(true) that doesnt seem to pass prototype methods either. is there a way to reapply the prototype methods or better yet clone them with the element
here is the entire slert function
function genSlert(params) {
var slerts = getSlerts(),
$slert = document.createElement('div'),
$buttons = document.createElement('div'),
$ok = document.createElement('button'),
$cancel = document.createElement('button'),
$content = document.createElement('div');
for(var i = 0; i < slerts.length; i++){
slideUp(slerts[i]);
}
$buttons.className = 'buttons';
$content.innerHTML = params.content;
$content.className = 'content';
$slert.className = 'slert';
$slert.appendChild($content);
$slert.appendChild($buttons);
$buttons.appendChild($cancel);
$buttons.appendChild($ok);
$ok.innerHTML = 'OK';
$cancel.innerHTML = 'CANCEL';
wrapper.appendChild($slert);
wrapper.classList.remove('hidden');
document.body.classList.add('no-scroll');
$cancel.onclick = function (e) {
if (params.cancel) {
params.cancel(e);
}
//close modal
closeSlert($slert);
}
$ok.onclick = function(e){
if(params.ok){
var $cont = $content.cloneNode(true);
params.ok($cont);
}
closeSlert($slert);
}
}

Find an anchor in a Div with javascript

In javascript I have a reference to a div. In that div is an anchor element with a name='foundItem'
How do I get a reference to the anchor with the name foundItem which is in the Div I have the reference of?
There are 'many' foundItem anchors in other divs on the page. I need 'this' DIVs one.
// assuming you're not using jquery or mootools
// assume div is mydiv
var lst = mydiv.getElementsByTagName('a');
var myanchor;
for(var i=0; i<lst.length; ++i) {
if(lst[i].name && lst[i].name == 'foundItem') {
myanchor = lst[i];
break;
}
}
// the mootools method
var myanchor = $(mydiv).getElement('a[name=foundItem]');
You can use the getElementsByTagName method to get the anchor elements in the div, then look for the one with the correct name attribute:
var found = null;
var e = divReference.getElementsByTagName('A');
for (var i=0; i < e.length; i++) {
if (e[i].name && e[i].name == 'foundItem') {
found = e[i];
break;
}
}
If found is not null, you got the element.
If you happen to use the jQuery library, you can let it do the searching:
var found = null;
var e = $(divReference).find('a[name=foundItem]');
if (e.length == 1) found = e.get(0);
Use a JavaScript library like jQuery and save yourself time.
var theAnchor = $('#divId a[name=foundItem]');
Using jquery, it's dead easy:
<script type="text/javascript">
$(function(){
var item = $("#yourDivId a[name=foundItem]")
)};
</script>
Update:
As per the comments, if you have control over what to id/name/class your anchor tag/s, it would be best to apply a class to them:
<div id="firstDiv">
test
</div>
<div id="secondDiv">
test another one
</div>
<!-- and so forth -->
<script type="text/javascript">
$(function(){
var item = $("#firstDiv a.foundItem");
alert(item.html()); // Will result in "test"
var item2 = $("#secondDiv a.foundItem");
alert(item2.html()); // Will show "test another one"
)};
</script>
If you're doing anything with javascript, jQuery saves you tons of time and is worth investing the effort to learn well. Start with http://api.jquery.com/browser/ to get an intro to what's possible.
Not sure if this helps, but wanted a function to handle the load of a page dynamically and scroll to the anchor of choice.
function scrollToAnchor(anchor_val) {
alert("" + anchor_val);
var page = document.getElementById('tables');
var found = null;
var cnt = 0;
var e = document.getElementsByTagName('a');
for (var i = 0; i < e.length; i++) {
if (e[i].name && e[i].name == anchor_val) {
found = e[i];
break;
}
cnt++;
}
if (found) {
var nPos = found.offsetTop;
alert("" + nPos);
page.scrollBy(0, nPos);
} else {
alert('Failed with call of scrollToAnchor()' + cnt);
}
}

Categories

Resources