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.
Related
I want to get a DOM element by clicking on it. The script looks like this:
document.onclick = function(e) {
e.target.style.backgroundColor = "green";
e.target.style.color = "white";
alert(e.target.tagName);
for (let attr of e.target.attributes) {
alert( `${attr.name} = ${attr.value}` );
};
};
The script works fine for the HTML elements, but it does not work for the elements generated by script. Actually nothing happens while I am clicking on the dropdown and selecting a value from it (no alert appears). Google Dev tools displays the element and some of its attributes including its id as far as I can see is autogenerated. I even can use its autogenerated id for doing automation job with this element. But is their any way to catch the element with my script?
Unfortunately I am not able to provide the original web page as it is internal company product, but I have found something similar that can be checked and by the way in this case it looks like the reason of not working script is different that those I have with auto generated elements in my original application. As you can see for the general Google page the script does not work for any element located on the page, but for the Google URL shown on the second image the same script works fine for all elements located on the page. And the shovn element itself is differnt (represented by div on the first image and by img on the second)
The reason it does not work for dynamically generated elements is simply because event listener is bound upfront on pre-existing elements.
You should have a function that re-binds the event every time a new element is added to the DOM. The function should be either targeted for the single element, or whole page.
window.onElementAdded = (element) = >{
element.onclick = ...
}
This function needs to be invoked every time an element is added to the DOM from your code.
I have a function that dynamically creates div elements based upon whatever input is given, and lets them choose certain items by clicking on each div. I have it so that if the div is clicked, a function (named checkToggle) is called that makes it looks like it is selected and adjusts some related variables. There is a checkbox in the div element that is toggled by this function (hence its name). Long story short, I had to jump through some hoops to get it to work, most of which I don't even remember. Please don't ask me about that.
The point of this question is this. I initially used the following JavaScript code to run the function when the checkbox was clicked. It was assigned by the main function, which created these div elements using a for loop.
document.getElementById(`${itemID}-checkbox`).onclick = function() {
checkToggle(`${itemID}-checkbox`);
};
This works, but I wanted to try to convert all of my onClick functions to JQuery. Here is the JQuery alternative I created.
$(`${itemID}-checkbox`).on(`click`, function() {
checkToggle(`${itemID}-checkbox`);
});
While the code itself seems to be fine, it does not work. It seems as if JQuery functions cannot be created like this in a for loop or something. It is applied after the element is created and put in its place, so I don't think it has anything to do with the element not being ready. I am also having the same issue with 2 other similar cases. Any idea as of why this isn't working?
Let me know if more information is needed and if so, what kind of information is needed.
You need to update the selector to Target HTML id using the # character. Simply prepend the character to the query:
$(`#${itemID}-checkbox`).on(`click`, function() { checkToggle(`${itemID}-checkbox`); });
It would also apply to DOM methods querySelector or querySelectorAll as well.
Hopefully that helps!
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 :)
Using mootools I have a 'builder' class that manufactures form objects, dynamically creating divs as it does so.
Some of the form objects are made up of several different objects. For example, a selection object features a textbox which filters the contents of the selector and a button to save the selection.
In this case I want the filter box and button to be located in a div which is appended to the div of the overall form object so as to have a 'wrapper'.
However, I'm having a problem appending to the div of the dynamically created form object.
After the dom is loaded, the 'builder' class is called:
window.addEvent('domready', function()
{
builder = new Build();
});
Builder creates a new div as such
var div = document.createElement('div');
var div_id = 'the_div_id_for_my_form_object';
div.setAttribute('id', div_id);
It then creates the form object which takes in the div as one of its parameters
var form_obj = superSelector(div);
Inside the form_obj constructor, this div is saved as a member variable, this.div = div.
The filter textbox is created as well as the button.
Here is where I'm seeing a problem. (since the issue is the same for both the filter textbox and the button, I'll describe only the textbox case)
The div of form_obj is passed to the constructor of the filter textbox.
When the filter textbox is created, it creates a div for itself
var div = document.createElement('div');
var div_id = 'the_div_id_for_my_filter_box';
When I attempt to append this div to the div of form_obj, I get a js error saying that I am attempting to append to 'null'
var filterBox = new Class({
initialize: function(name, form_obj)
{
this.name = name;
this.div = document.createElement('div');
this.div.setAttribute('id', name);
document.getElementById(form_obj.div).appendChild(this.div);
}
Yields:
"Uncaught TypeError: Cannot call method 'appendChild' of null"
I'm not sure how to get around this. I get the sense that the div I want to append to doesn't exist at the time I try to append to it. However I see no way of generating an event which tells me when it does exist so that I can postpone the construction of any 'child' divs until that point
right. several things you do that are not mootoolsy.
var div = document.createElement('div');
var div_id = 'the_div_id_for_my_form_object';
div.setAttribute('id', div_id);
should be:
var div = new Element('div', {
id: 'the_div_id_for_my_form_object'
}); // or even new Element('div#foobar');
div.setAttribute('id', div_id); -> div.set('id', div_id);
then appending to the dom:
document.getElementById(form_obj.div).appendChild(this.div);
why? what are you trying to do? grab an element and add to the div in memory?
document.id(form_obj.div).inject(this.div);
// if this element exist, it will be moved as a child to the new div, not safe
// you really ought to rewrite to:
var el = document.id(form_obj.div);
el && el.inject(this.div);
keep in mind this div is not injected to the dom yet at this point.
and so on. read the manual/api - you can always use native js but that kind of defeats the purpose of using a library that fixes things for you.
On a side note, doing what you are doing is not exactly easy, I am currently working with a friend (well, colleague!) of mine on something of a form-builder (for mootools, AMD) and it does what you will probably want to do, more or less - input types, groups, infinite dependencies triggered by values (on all el types), all sorts of form elements and custom looks / feels, validators, default values, placeholders, custom events.. Model/controller like behaviour, default values, server side data / validation, persisted per input data (sessionStorage / window.name)
gets created with AMD builder manifests that support versioning, pagination and languages over twitter bootstrap markup and elements and a single-page restful app via hashtags... basically, it is really a big task.
if we ever decide to open-source it (and I hope we can), and ppl have interest, who knows - you can pretty much build things like interactive tests, survey monkeys, quick forms, complex forms, whatever with it... its extendible and flexible. hope we finish it....
Try sending the "form_obj" parameter to getElementById instead of the div itself. The error seems to be indicating that the div cannot be found in the DOM by the method currently employed. getElementById takes the id attribute of the target div as a string.
e.g.
document.getElementById(form_obj).appendChild(this.div);
getElementById only takes a string parameter. It's returning null because you're passing in a reference to an element to the method.
I'd suggest you to get rid of getElementById. just use the reference you already have to the div you want to append the element to.
form_obj.div.appendChild(this.div);
Hope it helps.
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.