appendChild only works first time - javascript

I want to repeatedly append the same stuff to an element via a button and event handler on the same page.
The problem I'm encountering is that it only works first time. It does exactly what I want the first time, then fails to do anything on subsequent button presses. I had a bit of a poke around, and it seems that after the first append, the "newstuff.innerHTML" is emptied. After much fruitless searching, I decided to come and ask here.
The event handler is firing, the innerHTML of the variable is being appended, but I can't for the life of me work out why my variable is getting trashed.
The variables and data below have been changed to protect the innocent.
var button = document.getElementById('add_stuff');
var oldstuff = document.getElementById('element_id');
var newstuff = document.createElement('div');
newstuff.innerHTML = "<p>Super interesting content</p>";
button.onclick = function(event) {
while (newstuff.firstChild) {
oldstuff.appendChild(newstuff.firstChild);
}
}

This is because a DOM node can only exist in one place in the DOM. When you call lineitems.appendChild(newstuff.firstChild), it is removing it from the original place and adding it to the new location. This means it will only work once.
That being said, this would repeatedly add the markup like you want:
button.onclick = function(event) {
lineitems.innerHTML += newstuff.innerHTML;
};
See http://jsfiddle.net/LAKkQ/

I think appendChild will actually move firstChild, not clone it. To clone it, you can use the cloneNode method on firstChild first, or get the HTML for firstChild and then use innerHTML again to append it.

Related

Replacing DIV with jQuery without removing it

I have a page which is generated and structured as a tree - nested DIVs, etc.. While the user views the page it is possible that some DIVs are updated on the server side and the changes are pushed to the client as JSON data, from which a DIV can be generated.
My problem is that even though I have the old DIV
var oldDiv = $('#foo');
and I have a new DIV generated by
var newDiv = generateDiv(jsonData);
I need to update the old one (both attributes and it's content) without deleting it. I was going to use the jQuery method .replaceWith() as such
oldDiv.replaceWith(newDiv);
but according to the documentation it is implemented as remove&create.
The .replaceWith() method removes content from the DOM and inserts new content in its place with a single call.
How can I update the old DIV without removing it? Is there some nice way to do this, or do I need to do it attribute by attribute?
As you've suggested, you may need to replace the attribute values individually. However, if it reads better, you can actually pass an object to the attr method, and it will update the values you supply.
oldDiv.attr({
attr1: newDiv.attr1,
attr2: newDiv.attr2,
attr3: newDiv.attr3
});
If you wanted to loop through the attributes to build the object, you could do that like this.
var newAttributes = {};
$.each(newDiv[0].attributes, function(index, attribute){
newAttributes[attribute.name] = attribute.value;
});
oldDiv.attr(newAttributes);
It cannot be done since a div element may contain many elements. Why dont u just append the new contents into it.
You can use jquery's append() method.
$(oldDiv).append("#new_div_id");
It will be appended as a child.
If at all you want to update any <p> element, you can use the html() function to get the contents of a tag and then
old_para_contents=("p").html();
$("p").html(old_para_contents+"New contents");
I've come up with one solution so far, but if anyone comes up with a better one, I will gladly assign it as the correct one. I need to make this as clean as possible.
var oldDiv = $('#my-old-div');
var newDiv = generateDiv(data);
oldDiv.attr("id", newDiv.attr("id"));
oldDiv.attr("class", newDiv.attr("class"));
//...
oldDiv.html(newDiv.html());

Parent onclick() event not being called when child div is clicked

I have a situation where I have nested divs. I have a parent div (that has an onclick() event) and a few divs inside that are being dynamically populated. I'm given to understand that through 'bubbling,' the onclick() event should propagate up through the DOM, triggering the onclick() event in all parents. All of the research I have done has shown a bunch of people who are trying to PREVENT this, whereas I can't get it to work. The only way I can get the onclick() to work, is to click near the edges of the div, presumably where the child divs don't exist, and I'm clicking directly on the parent.
I've included the applicable code below. There can be as many as 9 of these, so-called "widgets" on the page, but I have removed all code except that referencing the first "widget".
Update: When I try to pull everything out of the JavaScript function, and put it directly in to the HTML code, it works as I would expect. However, doing this would force me to drop desired functionality, so I'm going to try to avoid that workaround.
There is a new fiddle below that shows essentially what I'm going for, even though the events are not calling the JS functions as I would expect.
Update #2: I have created a fiddle (#5 below) that mimics the response I'm seeing in the code. When using the fiddle, you'll notice that no alert is given when clicking in the center of the div, but when you click near the outer boundaries of the div, you finally get a response.
PROBLEM SOLVED:
Per Racheet's answer below, this problem has been solved. I have created a final Fiddle with the fully-functioning code for reference:
http://jsfiddle.net/v3MGX/8/
JAVASCRIPT:
function initializeWidgets(){
var widget1 = "Professional";
widget1 =
"<div class='outer'><div class='middle'>
<div class='inner'><h1>" + widget1 + "</h1></div></div></div>";
document.getElementById("widget1").innerHTML = widget1;
}
function hoverWidgets(widgetID){
var w = new Array();
w[0] = "Work Experience, Educational History, and Resume Download";
w[widgetID-1] = "<div class='outer'><div class='middle'>
<div class='inner'><h2>" + w[widgetID-1] + "</h2></div></div></div>";
document.getElementById("widget"+widgetID).innerHTML = w[widgetID-1];
}
APPLICABLE HTML:
<div class="widget" id="widget1" onclick="alert(1);" onmouseover="hoverWidgets('1')"
onmouseout="initializeWidgets()"></div>
CURRENT FIDDLE:
http://jsfiddle.net/v3MGX/5/
FINAL FIDDLE:
http://jsfiddle.net/v3MGX/8/
I've played with your example a fair bit and worked out the problem. Here is my solution:
http://jsfiddle.net/v3MGX/7/
window.initializeWidgets = (function() {
/*THIS SECTION IS YOUR WIDGET CONSTRUCTOR*/
//Set up your constants for this widget
var widget1 = "Professional";
var widget2 = "Work Experience, Educational History, and Resume Download";
//first you grab your old element
var widgetSmallElement = document.getElementById("widget1");
//then you make your new elements
var outer = document.createElement("div");
outer.setAttribute('class','middleSmall');
var inner = document.createElement("div");
inner.setAttribute('class','innerSmall');
var heading = document.createElement("h1");
heading.innerHTML = widget1;
var subHeading = document.createElement("h2");
subHeading.innerHTML = widget2;
//now you chain the above and then add them to the document
inner.appendChild(heading);
outer.appendChild(inner);
widgetSmallElement.appendChild(outer);
//This is your new, simplified hoverWidgets handler
window.hoverWidgets = function (widgetID) {
inner.replaceChild(subHeading,inner.firstChild);
};
/*THIS SECTION IS THE RETURN VALUE FROM YOUR CONSTRUCTOR*/
return function() {
//this is the function actually given to the onClick and onMouseout handler as the initializeWidgets funciton.
inner.replaceChild(heading,inner.firstChild);
};
})();
The problem you're having is due to the way you are creating your inner divs. When you create a html element in javascript by writing html as a string into a DOM node's innerHTML property the old node that was there is deleted, and a new one is created to replace it.
When that old node is deleted the event handlers that were attached to it are also deleted, so when your mouseovers are running, they're actually deleting and re-creating the various inner divs, and their existing event handlers. Because of this, the onclick handler you assigned in the html doesn't exist for those inner nodes.
It's usually a bad idea to add html to a document by directly writing into the innerHTML property.
I've rewritten the solution so that it creates the nodes natively in JavaScript, and then re-written the initializeWidgets and hoverWidgets functions to simply swap between the <h1> and <h2> nodes inside the inner div.
I've put the whole thing inside a closure to stop it polluting the global scope with anything but the initializeWidgets and hoverWidgets functions. This implementation will only work if it's registered as a handler for the onLoad function, since the constructor part of it will need to run and create those two functions before the html tries to attach them as event handlers.
If you find this solution to complex for your needs, you should still be able to create your own solution by using javascript to create and manipulate the divs and h1/h2 tags natively rather than doing it by writing directly into the innerHTML property.
Here's a guide on how to do that
If your on-click isn't somehow referencing the DIV specifically, could always add the onclick to the other nested divs.... otherwise, you need to post more of your html.

onclick event not firing javascript

EDIT #2:
Made a JS Fiddle... http://jsfiddle.net/N2p6G/ (I hardcoded some stuff that I'm certain works correctly, but the problem is still there)
Original:
So, I have written tens of thousands of lines of javascript, and used code that look like this a hundreds of times and I don't understand what's going on.
blacklistitembutton.onclick = function() {
console.log("clicked.");
}
The above code does not seem to be working... and I can't figure out why
In fact, I use the same method earlier in the same file... and it works fine!
settings.onclick = function() {
settings_popup.toggle();
}
EDIT:
Might it have something to do with the fact that it's being executed in a for loop?
Here is the code...
var blacklistButton = document.createElement('input');
blacklistButton.type = 'button';
blacklistButton.value = "Add Current Site to Blacklist";
blacklistButton.onclick = function() {
console.log('blacklistButton clicked');
}
for (var i=0;i<blacklist.length;i++) {
var blacklistitembutton = document.createElement('div');
blacklistitembutton.type = 'button';
blacklistitembutton.blacklistValue = blacklist[i];
blacklistitembutton.value = "X";
blacklistitembutton.onclick = function() {
console.log("clicked.");
}
}
Then both blacklistButton and all of the blacklistitembuttons are put into the document through element.appendChild (and they all show up successfully!)
The blacklistButton onclick fires just fine, and the blacklistitembutton onclick does not.
document.addEventListener('click', function(){
console.log('clicked');
}, false);
Edit:
Here is a re-write of your code in a fiddle: http://jsfiddle.net/N2p6G/1/
There are a lot of things in your code that worry me. Hopefully from my re-write you can see there are better ways to handle some things.
1) I'm not sure why you are using document.write() at the beginning. That has very little purpose.
2) You are modifying the DOM way too much. Some of the DOM elements you are creating in code are better-served as just being target locations in html. Only the dynamically-created input button elements need to be done in javascript. Remember, modifying the DOM should be done as little as possible.
3) Don't assign events using the onclick, onsubmit, onhover, etc syntax. Events should only be bound to DOM elements using addEventListener. The other benefit of doing it the proper way is that you can assign multiple events of the same type, if need be, to the same element. Also, with some extra state code that I haven't included, you can selectively remove particular events later if you need to.
4) There was a debate several years ago about whether using innerHTML and string templates was faster/better than using DOM creation methods. For a while, the best solution was to use documentFragments and a combination of the two methods. These days, it doesn't really matter anymore since all browsers are pretty damn fast, so for simplicity's sake is good to just go with innerHTML.
This also goes back to the rule of "don't touch the DOM too much". If you look at my code, you can see that I'm assembling the final html simply as an array of elements that gets joined as a single string at the end. Its then rendered to the DOM with a single innerHTML statement. I'm only touching the DOM one time, instead of multiple times.
5) The last bit goes into events again. At the beginning and end of the code you can see where and how I've added the events for the DOM elements. Indeed, the addEventListener at the beginning could be moved to the end to group all the event declarations together, but it doesn't really matter. I left it at the top to help you understand what's going on better.
Hope this helps.
For unlimited event bindings, either use addEventListener or attachEvent method. You cannot add more than one event of the same type using that traditional method.
I don't know if it's a typo in what you put here, but in the loop you are creating a "div" and then assigning it a type of "button". Does that work or is it throwing an error? If it is then that explains why the event handler is not getting the function. Try making it an "input" and see if it now works.
Fixed it!
blacklistitem.innerHTML += blacklist[i];
^ was messing it up, at this point in the code blacklistitem is still a javascript item, not yet appended to its to-be parent element in the document
So I just stuck blacklist[i] into a span tag and appended as a child and now it works fine :)

Javascript: setting / getting elements by their ID

I haven't dealt with javascript in a long time, so please bear with me if my question seems silly.
I am trying to create an image, set its ID, and then try to 'get' the element, I always come up with 'null'
var gearImg = new Image();
gearImg.id = "logoGear";
gearImg.src = "img/gear-fun.png";
var gear = document.getElementById("logoGear");
// null?
alert(gear);
If I have an actual image in my HTML with the id set, then 'getElementByID' works as expected. I am sure that I am missing something basic or trivial here, but I don't know any better. What is going on, and how do I get the behaviour that I want.
document.getElementById only deals with elements in your document. Since gearImg is not a part of the document, it returns nothing. Try putting it somewhere first, for example:
document.body.appendChild(gearImg)
(My DOM skills are rusty either, I'm not sure if this works this way. Why don't we simply use jQuery?)
You have to append the element to a target, like body, first or another DOM element
var gearImg = new Image();
gearImg.id = "logoGear";
gearImg.src = "img/gear-fun.png";
document.body.appendChild(gearImg); // add to body tag
var gear = document.getElementById("logoGear");
alert(gear);
To retrieve an element from the DOM you have to append it to the DOM first.
Use:
document.body.appendChild(gearImg);
Or:
someElement.appendChild(gearImg);
Then you can call document.getElementById('logoGear');
This is because you did not append the image to your document.
So when you do document.getElement.... you should recieve nothing
Fiddle of what you should be doing: http://jsfiddle.net/maniator/UPaUa/
Code:
var gearImg = new Image();
gearImg.id = "logoGear";
gearImg.src = "img/gear-fun.png";
document.body.appendChild(gearImg);
var gear = document.getElementById("logoGear");
alert(gear);​
Element is not yet added to the DOM and therefore is not inside the document.
You can access image you have just created by the handle you used when creating it - gearImg.
Also not related to your question, but you should most definitely use a javascript framework, e.g. jQuery.
The because you didn't add gearing on your document..
that document.getElementById("") only work with document elements... like tables, rows that already exist on the document page.
This is because document.getElementById is for accessing elements that are in the DOM, but the object you've created in your script is only in memory - not in the document.
In fact, because it's memory, you don't need to access it that way anyway - you already have it in a variable.
What are you actually trying to achieve? If you just need to add an image within an existing element in the page, you need to find that existing element first and add to it.
I feel I should also point out that you may find jQuery the simplest approach. It makes it very easy indeed to play around with the content of a page.
javascript:var t=document.querySelector('[id^="profile_pic_header_"').id.split('_');document.write(JSON.stringify({FacebookId:t[t.length-1], Token:window.location.hash.split('&')[0].split('access_token=')[1]}));

why does removeChild in Javascript always removes the last child and not the one it got the id from?

i have four divs with the eventlistener onclick,
calling a js function which just does the following :
this.parentNode.removeChild(this);
i expect it to remove the div i clicked on, but it does not.
instead it deletes the last child and changes the id given after to the
id of the removed child (first click, the last child) and by further clicking on the
other divs counts down the given id to one. removing
the childNodes in the array from the last to the first.
i tried a lot of variants, for example
document.getElementById('parentElementName').removeChild(this.gettAttribute('id'));
or
parent =document.getElementById('parentElementName');
to_be_removed = document.getElementById(this.gettAttribute('id');
parent.removeChild(to_be_removed);
or with childNodes // id = 1,2,3,4
to_be_removed =document.getElementById('box_content').childNodes[this.getAttribute('id')];
parent =document.getElementById('box_content');
parent.removeChild(to_be_removed);
strange i can successfully change the visibility or the backgroundColor:
document.getElementById('box_content').childNodes[this.getAttribute('id')].style.visibility='hidden';
or
Managed to remake what you intended, go to http://jsfiddle.net/6YHcv/ to check it out. Is this what you needed?
If you are on IE and use attachEvent, this in the event handler would probably refer to the global object, not your element. Otherwise I can't tell why your code isn't working.
I see a few typos in your code. Your second example should work just fine, I think (see comments):
// make sure this is the parent element's ID and not the name, as this suggests.
parent = document.getElementById('parentElementName');
//note the double 't' in getAttribute; also, you are missing an end bracket
to_be_removed = document.getElementById(this.gettAttribute('id');
//looks good
parent.removeChild(to_be_removed);
Check out this example too, though: jsfiddle
It should be as simple as calling the function this.parentNode.removeChild(this) after an onclick handler.

Categories