I have multiple div's on my page, that can be added to the page dynamically, and can also be removed.
On page load, the dynamically created div's are loaded from localStorage with uniqiue id's and a common class depending on the div, and I call a function along with that, content().
The function content() looks like this:
function content(){
alert("test");
$(".two button").click(function(){
var id = $(this).parent().attr("id");
alert(id);
});
}
Pretty simple, all it does is alert "test" when the function is called, and if you click .two <button>, it will alert .two <textarea> .val().
This works fine once the div's have been loaded, but I run into the problem when I clone the div's
When I clone the div's, it gives them a unique id and a common class like above. At the end of cloneDiv(), I call content(), so that clicking on the elements inside will produce the same results as above.
The problem is, the function will get called as many times as there are div's on the screen, but also means that clicking on the <button> in div .two will alert .two <textarea> .val() three times.
TLDR; Clicking on the button in .two get's called as many times as there are div's on the screen, as the function is called once the dynamic div is created, but should only be called once.
There's a lot of code to do all this, but I think I explained what happens pretty clearly. I will however whip up a demo if that would help more.
You want to use the .on function:
$('body').on('click', '.two button', function(){
var id = $(this).parent().attr("id");
alert(id);
});
I would change the 'body' part of that to the real container of these buttons. So say the buttons are always inside a content div with id my-div, you'd do:
$('#my-div').on('click', '.two button', function(){
var id = $(this).parent().attr("id");
alert(id);
});
You also only need this code to run once, so no need to put it in a function that gets called multiple times. Just put it in your $(document).ready(....).
A fiddle: http://jsfiddle.net/gromer/dxbqz/
This is what "event delegation" is for. You set up a handler on the document and it will handle events from its descendants, even if they are created after the handler is attached:
$(document).on("click", ".two button", function(){
// put a click hander on the document to process clicks
// from buttons that are descendants of elements with class=two
The problem is this: "At the end of cloneDiv(), I call content(), so that clicking on the elements inside will produce the same results as above." What happens is that you are compounding the events on to the existing elements so that is why you get so many callbacks. You should instead use the .on function, and use it only once. If you call .on multiple times it will produce the same results because multiple events will be registered.
$(".two button").on("click",function(){
var id = $(this).parent().attr("id");
alert(id);
});
Do this once, every button matching (".two button") will respond to it, even if appended later.
Another approach would be to pass in the element to the content() call and then mark it up from there if you are always calling this function when creating the elements anyway.
function content(element){
$(element).click(function(){
var id = $(this).parent().attr("id");
alert(id);
});
}
As a side note, using alerts to debug can be problematic as they interrupt program execution. I would highly recommend using console.log(id) instead and then looking in the console for your testing variables. console.trace() is also a good one to keep in mind if you are using a third party script and need to see what went wrong where.
Related
I've the following html structure
<body data-page="first">
<div class="start">Test</div>
</body>
and the following js
$('body[data-page="first"] .start').on('click',function (){
body.attr('data-page','second');
});
$('body[data-page="second"] .start').on('click',function (){
console.log('Test');
});
I would expect, that after the second click on .start, the console would show "Test", but it doesn't...
Can you tell me what I'm doing wrong?
Thanks in advance!
While you have your answer, I don't think the essential point has been made in any of the answers so far, and that is that the binding of an event handler must happen after the target element exists.
When you try to bind an event handler to a particular element in the DOM, the element must exist at the time. If it does not exist, the handler has nothing to bind to, and so the binding fails. If you later create the element, it's too late, unless you re-run the binding statement.
It will soon become second nature to call appropriate event handler binding statements after you create a new element (by modifying the HTML using javascript) that needs a handler.
For instance, in my current project I regularly make AJAX calls to a server to replace blocks of HTML as things happen on the page. Even if some of the new elements are exactly the same as the ones being replaced, they will not inherit any bindings from the replaced elements. Whenever I update the HTML I call a function that contains necessary statements to bind my event handlers to the new copy of the active elements.
Your code would work if you made the following change:
$('body[data-page="first"] .start').on('click',function ()
{
body.attr('data-page','second');
$('body[data-page="second"] .start').on('click',function (){
console.log('Test');
});
})
A couple of other (off-topic, but related) points:
It's possible to bind a handler to an element multiple times. The trick to avoiding this is to include the .off() method in the chain before binding (noting though that .off("click") will unbind all click handlers bound to that element, not just yours) e.g.
$("#mybutton").off("click").click(function(){myHandler()});
"the arrow function doesn’t have its own 'this' value" () so don't use arrow functions in event handlers if you plan to reference any of the element's properties via 'this'. e.g.
$("#mybutton").off("click").click(() => {console.log(${this.id})}); // >> "undefined"
The issue is that the page is rendered with the data-page set to first, and when you click again on it, that part of javascript still see "first", since is not rerendered, so you need a dynamic function, the read all the intereaction with that button, and than check wich value that attribute has. Like this you can make infinite cases, and still go on.
$('body .start').on('click',function (){
const attr = $('body').attr('data-page');
if(attr === 'first') {
$('body').attr('data-page','second');
} else {
console.log('second');
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<body data-page="first">
<div class="start">Test</div>
</body>
And if you don't like the fact that is targetting all the "body" wich is weird, becouse you should have only 1 body, you can use an ID to target the right one
PS: is never a good idea to duplicate your function, if you can set everything in a dynamic function, that reads everything, is easier to debug in the feature, and is lighter and more clean to work on
$('body[data-page="first"] .start').click(function (){
var body = $('body[data-page="first"] .start');
body.attr('data-page','second');
});
This method can help :
var timesClicked = 0;
$('.start').on('click',function (){
timesClicked++;
if (timesClicked>1) {
console.log('Test');
}
});
I have a script which is obscured (ie cannot read the div elements from script). Upon rendering in inspect DOM element I can locate what the div I want to assign a function to. However I cannot do this. Do I need the div id within the script (ie obscured div id)?
I want to assign click on function and plan do it as follows:
<script>
$(document).ready(function () {
$('.contentoverlay').on('mouseover', function() {
click();
});
});
</script>
The script in itself is very long so I won't paste it here.
Overall I want to know:
Do I need to locate the obscure script's div is and assign my click function to it
Is the function above correct
If there are multiple overlays do I have to assign the function above to each one (the overlays in themselves are mouse pointer none, so they do pass down clicks, but would the function above get passed down or even blocked if I don't assign it to all the overlays)?
example: http://output.jsbin.com/joxetuc
First, i want explain my comment first, then i will answer your 3 questions in the end
A) honestly i have bad english here, i dont know what "obscured" means (even i google this), but i think that's means new DOM that newly created (dynamic created element) by javascript. like this:
started DOM <table></table>
New DOM <tr><td>texttt</td></tr>
the result <table><tr><td>texttt</td></tr></table>
if this not correct, my whole answer below will be wrong.
B) So, in jsbin example, i only give you div#container. And then create new DOM inside div#container when you click created button.
C) i create 1 function to handle if new DOM is clicked, which will write $(element).text() to div.log
//function to execute if element clicked
$('.container').on('click', '#newid', function(i,e){
var txt = $(this).text();
$('.log').append(txt+' ');
});
D) i handling event mouseover too with same function in click function. Because i'm lazy, i only write $(this).click(); which $(this) is refer to div#newid
//if mouseover, perform click
$('.container').on('mouseover', '#newid', function(i,e){
$(this).click();
});
so, in that jsbin i show you how to handle mouseover new DOM to execute click() function.
Now, Your Question
1) Do I need to locate the obscure script's div is and assign my click function to it?
You dont need it, as long as you know how to locate that element.
2) Is the function above correct?
that's not correct function. the correct 1 is:
parentElemenentOfNewDOM.on('mouseover', newDOMElement, function() {
$(this).click(); //this will perform your default click function that you give before
});
3) If there are multiple overlays do I have to assign the function above to each one (the overlays in themselves are mouse pointer none, so they do pass down clicks, but would the function above get passed down or even blocked if I don't assign it to all the overlays)?
you dont need to assign same function to handle multiple element (overlays). What you need is the correct script to handle all of your new multiple element. And my script will handle all of the new element.
if you want to know the different, see this DEMO http://output.jsbin.com/vukudu
I have a fiddle which creates a viewer for a set of data. If you are looking at the javascript, it will look at 3 lines, if you search for SEARCH_HERE
$("body").append("TEMPLATE<hr />Maintaining Object").append($maintence);
//$("body").html($maintence);
//$("body").html($_table);
The fiddle is located at: http://jsfiddle.net/fallenreaper/wFGW6/1/
The first one will show the TEMPLATE on the page and then adding new ITEMS will all have working events when doing
var $data = $_table.clone(true,true);
in the addBlock() function.
If you only uncomment the second line, it will JUST show the maintainer item.
When you add items [+], you will show the form, but the events would not be there.
I was thinking that since $_table is removed from the page, the events are not there any longer. The 3rd line, pretty much reappends $_table to the document, and the events are not there.
IS this suppose to be like this? Should i instead just create a wrapper function which is executed inside of addBlock() to attach all the handlers accordingly?
This is rather odd.
EDIT:
One answer, pointed to delegated events, which seems like it could work. There is an issue though that seems to set $(this) to a new object, the body tag, instead of the selected element.
inside of a click event would be redefined as:
$("body").on("click", $expander, function(){...});
//instead of:
//$expander.click(function(){...});
I was thinking to just do something like left-hand assignment, something like:
$(this) = $expander;
but according to a website, left-hand assignment doesnt work. (http://hungred.com/how-to/tutorial-override-this-object-javascript/). They did point me in a direction which would be VERY useful.
function example(eventHandler){
ALL MY CODE.
}
$("body").on("click", $expander, function(event){
example.call($expander, event);
});
Does this look feasible, or should i be planning another route?
You can use jQuery's .on() to do some event delegation. Your code is too long for me to read and edit, but in a nutshell rather than having $add.click(function() { ... }) you bind the event listener to the parent or body $("body").on("click", ".addNew", function() { ... })
I have the following code:
var $reviewButton = $('span.review_button');
$reviewButton
.live('click',
function(){
$('#add_reviews').show();
}
)
Later in the script, I use an AJAX call to load some content and another instance of $('span.review_button') enters the picture. I updated my code above to use '.live' because the click event was not working with the AJAX generated review button.
This code works, as the .live(click //) event works on both the static 'span.review_button' and the AJAX generated 'span.review_button'
I see however that .live is depracated so I have tried to follow the jquery documentations instructions by switching to '.on' but when I switch to the code below, I have the same problem I had before switching to '.live' in which the click function works with the original instance of 'span.review_button' but not on the AJAX generated instance:
var $reviewButton = $('span.review_button');
$reviewButton
.on('click',
function(){
$('#add_reviews').show();
}
)
Suggestions?
The correct syntax for event delegation is:
$("body").on("click", "span.review_button", function() {
$("#add_reviews").show();
});
Here instead of body you may use any static parent element of "span.review_button".
Attention! As discussed in the comments, you should use string value as a second argument of on() method in delegated events approach, but not a jQuery object.
This is because you need to use the delegation version of on().
$("#parentElement").on('click', '.child', function(){});
#parentElement must exist in the DOM at the time you bind the event.
The event will bubble up the DOM tree, and once it reaches #parentElement, it is checked for it's origin, and if it matches .child, executes the function.
So, with this in mind, it's best to bind the event to the closest parent element existing in the DOM at time of binding - for best performance.
Set your first selector (in this case, div.content) as the parent container that contains the clicked buttons as well as any DOM that will come in using AJAX. If you have to change the entire page for some reason, it can even be change to "body", but you want to try and make the selector as efficient as possible, so narrow it down to the closest parent DOM element that won't change.
Secondly, you want to apply the click action to span.review_button, so that is reflected in the code below.
// $('div.content') is the content area to watch for changes
// 'click' is the action applied to any found elements
// 'span.review_button' the element to apply the selected action 'click' to. jQuery is expecting this to be a string.
$('div.content').on('click', 'span.review_button', function(){
$('#add_reviews').show();
});
I'm sorry for my bad english..
I did a jQuery function that when the user calls the function, it's creating an element on the page. To that elemet I did a jQuery click event and its working fine,
But if the user calls the function 3 times like this:
$.someFunction();
$.someFunction();
$.someFunction();
If the user clicks the first element, it will do 3 times whats in the click's event callback function, and if the user clicks the second element, it will do 2 times whats in the click's event callback function and if the user do it on the last one, it will do it once.
I want that when the user click every element then the callback function will happen just one time.
How can I fix that problem?
Bind the event to the created element rather than all existing elements.
$.somefunction = function() {
$("<div />").text("click me!").bind("click",function(){
alert("Hello World!");
}).appendTo("body");
}
Try doing something like:
$(".element").unbind("click").bind("click", function(){
alert('a');
});
Even easier: use jquery.on
$('.some-selector').on('click', function()
{
// do your work here
});
With jquery on ("live" in old versions) it will bind to any element matching that selector, regardless of when it is added to the DOM.