I have a function which dynamically creates an element and attaches a click event to this new element.
In the current state of my app, this function is called 5 times: for the 4 first created elements, all works fine, but the 5th one has no event attached!
I insist: I'm not merely saying that click doesn't work: Using $._data(myElemn,'events') in the console, I get Object { click=[1]} returned for the 4 working elements, but "undefined" for the last one.
Here is the code.
But I don't think is where the problem lies: since it works for other elements, It seems that the difference should come from the particular context of the 5th element.
So my question is rather: can we imagine which particular conditions may cause and event not to be attached (obviously,without any error message).
var createDDT= function(element) { /*
---------
Creates a drop-down toggle button embedded into element.
*/
$(element).css({position:'relative'}) // (relative: since DDT pos is absolute)
.append(
$('<span \/>').addClass(DDT)
.css({display:'none',})
.append($('<span \/>'))
// when click, toggle submenu:
.click(function(event) {
// hide or show current %Submenu:
var submenu=$(event.target).closest('li').find(jqSUBMENU);
submenu.toggleClass(OPEN);
// hide any other %Open %Submenu:
$(jqOPEN).not(submenu).removeClass(OPEN);
setTimeout(liveWidthDisplay,_params.cssTimeout); // adjust LWD's position
return false; // avoid following link, if embedded in <a>
})
);
}
[EDIT] As I previously said, the issue resides probably outside of the function. To emphasize it, in my app I tried replacing the code by the following:
var createDDT= function(element) { /*
---------
Creates a drop-down toggle button embedded into element.
*/
$(element).css({position:'relative'}) // (relative: since DDT pos is absolute)
.append(
$('<span \/>').addClass(DDT)
.css({display:'none',})
.append($('<span \/>'))
// when click, toggle submenu:
.click(function(event) {
alert(event.target.id);
})
);
}
Then the result is unchanged: $._data(myElem,'events') returns "undefined".
Unfortunately, I can't realistically add a significant context into jsFiddle, since it is a huge app.
can we imagine which particular conditions may cause and event not to
be attached
Yes, either you aren't actually passing your 5th element to the createDDT function - or else you have some other code that is destroying the event handler - possibly innerHTML or some such.
In any case a better way to handle this, that will ensure you get the event is to attach a single event handler higher up in the DOM. For example on the body (although any parent element that exists in the DOM will do).
// presuming DDT is a classname such as ".foo"
$('body').on('click', DDT, function(event) {
alert(event.target.id);
});
This way you have a single event that you don't need to create for each element regardless of it is dynamically added to the DOM or not.
Related
So I came across this weird issue and I don't know if I'm blatantly missing something. Visit this page (or any medium article). Open console and inject the following JS code.
for (const elem of document.querySelectorAll('.progressiveMedia-image.js-progressiveMedia-image')) {
elem.addEventListener('click', function () {
console.log(event.target);
});
}
Now click on the big images, expected behaviour should be (please correct me cause I seem to be wrong) that the target element is printed when you click on it the first time and also the second time. But as it turns out when you click on the image (zoomed) the second time (to zoom out) it doesn't print the target in the console.
I thought that there might be some overlay element and hence I bind the event on body to capture all of the events using the following injected JS.
document.body.addEventListener('click', function () {
console.log(event.target);
}, true);
But even with that I only get one console print of the target.
One theory for delegation using body not working might be following -
The newly created element would not be in the body in its time of creation, it will be moved to its place in the DOM tree later on. And hence delegation is not able to find it when did via body but able to capture it via document.
After a bit more exploring and injecting the following JS (taken from here and I know break point can be added, I did do that earlier but to no end so resorted to this.)
var observer = new MutationObserver(function (mutationsList, observer) {
for (var mutation of mutationsList) {
if (mutation.type == 'childList') {
console.log('A child node has been added or removed.');
}
else if (mutation.type == 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
});
observer.observe(document, {
attributes: true,
childList: true,
subtree: true
});
I don't see any element being added to the DOM on click (it is being added on load) so that theory might not be correct. So I guess now the real question is why Event Capturing through document is able to find the click event where else not from body. I don't think delegation works on initial DOM structure since it would break the purpose.
Also if it is a duplicate please let me know, since I don't really know what to exactly search for.
probably because there is something in front of the zoomed image that intercepts the click event in capture mode and stops propagation.
I've got success with this
document.addEventListener("click", function(e){ console.log(e.target); }, true);
The image (you are trying to target) is dynamically made. After you already clicked the image once you should be able to target it.
document.querySelectorAll('.progressiveMedia-image.js-progressiveMedia-image')
This queries the DOM for all elements that have both the class progressiveMedia-image and js-progressiveMedia-image. You iterate over the result and bind an event listener to the click event of each element.
When you do click on one of the images, the JavaScript that is already running in the page creates new elements and displays them. These new elements might have the same classes, but did not exist originally when you searched the DOM. As such, they do not have their click event bound.
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 clickable item, which contains child elements, too. My problem stems from wanting to animate the :active state of the clicked item by using a 1px translation.
The mousedown of the click lands on a child element
The 1px translate moves the child element from under the mouse pointer
The mouseup event now happens on the parent element
This results in the click getting canceled. I can prevent the child element from catching the click using pointer-events: none in CSS, but since it's both new and unstable, I'd love to find a more compatible fix. For now, I've settled on just sticking a transparent DIV on top of the whole item, but that's ugly.
Exaggareted demo in this pen: http://codepen.io/JonFabritius/pen/mJuzy
Try clicking the bottom half of the orange bar, the pointer remains on top of the child element. Then click on the top half, which causes the element to move from under the pointer.
It's probably staring me in the face, but I haven't been able to find a simple fix- any ideas appreciated.
Disclaimer: I don't jQuery.
This is a response better considered as a comment, though with the addition of code.
You say that "the click target is the parent" - Sorry to be a little thick here, but when you say it's the click target, is the parent the element that has the event listener attached to it, or by click target, do you mean that it is the value returned by evt.target? (Where evt is the single variable passed to the event handling function)
To better illustrate what I'm getting at, consider the following code:
JS portion:
window.addEventListener('load', mInit, false);
function mInit()
{
document.getElementById('clickTarget').addEventListener('click', handleClick, false);
}
function handleClick(evt)
{
console.log(this.id + ': is the id of the "this" element');
console.log(evt.target.id + ": was the id of the evt.target");
}
HTML portion:
<body>
<div id='clickTarget'><a id='link1'>Link1</a><a id='link2'>Link2</a></div>
</body>
Now then, when you click on link1, the output shown in the console is:
clickTarget: is the id of the "this" element
link1: was the id of the evt.target
Predictably, clicking on link2 shows the following:
clickTarget: is the id of the "this" element
link2: was the id of the evt.target
So, you can clearly see that in this example, the <div> is the parent of the two <a> elements.
Yet, in each instance the target is different - it's not the parent, it's the actual element that was clicked.
You've used jQuery, which, while simple to add functionality, hides implementation details, slows JS execution and (usually) adds unnecessarily to page-weight. Your code is very short and sweet, yet what is being returned requires some investigation - it's certainly not obvious.
As a side note, a quick look at the jQuery Docs: jQuery .delegate tells us that as of jQuery 1.7, the preferred method use to achieve this functionality is .on() - it's likely of no consequence, but one never knows..
Turns out if you use the current .on syntax, it works fine:
$(function() {
// target ON event, delegate
$('#container').on('click', '.item', function() {
$('.item').append('*');
});
});
UPDATE Could you do this with a custom class? This seems to work so long as the pointer doesn't leave the .item element. Child elements behave as expected.
$(function() {
$('#container').on('mousedown', '.item', function() {
$(this).addClass('active');
$('.item').append('*');
}).on('mouseup', '.item', function() {
$(this).removeClass('active');
});
});
DEMO: http://codepen.io/anon/pen/lponk
I hit a problem when using jQuery's Dialog widget...
I have a solution, but wondered if there was a more standard way (or I had mis-understood something):
Background
I have a web site that makes heavy use of AJAX, in that most of the time only portions of the page are updated. One portion of the page contains some JS that opens a dialog. When flipping between that portion and another, on opening the dialog for a second time things get messed up.
Reason
$el.dialog() removes the DOM element that is to become the popup ($el[0]) from its original place in the document hierarchy and appends it to the document body instead. When I then remove the popup element's original parent element, the popup element doesn't get removed.
This then means that doing this (changing / removing that portion of the page and then changing it back) all again results in duplicate element IDs which unsurprisingly confuses the hell out of the dialog widget.
Solution
I have come up with a solution that overrides the $.fn.dialog function and makes use of jQuery special events. It attaches a listener to the custom event 'destroyed' on the original parent element, the 'destroyed' event is triggered when jQuery removes any element, the listener reacts to this event by removing the popup element wherever it now might be in the document heirarchy.
Here it is:
(function($) {
$.event.special.destroyed = {
remove: function(o) {
if (o.handler) {
o.handler.apply(this, arguments);
}
}
};
var originalDialogFn = $.fn.dialog;
$.fn.dialog = function(firstArg) {
if (!this.data('dialog') && firstArg != 'destroy' && !this.data('dialogCleaner')) {
this.data('dialogCleaner', true);
var $parent = this.parent();
var $dialogEl = this;
$parent.bind('destroyed', function(e) {
if (this == $parent.get(0)) {
$dialogEl.remove();
}
});
}
return originalDialogFn.apply(this, arguments);
};
})(jQuery);
Are there any better ways of doing this? It seems like a slight flaw in the way the jQuery dialog works, in that it's not that easy to tidy it up nice and generically.
Of course I am aware of the dialog('destroy') method but doesn't seem particularly easy to hook that into my page fragment/portion handling.
You could do what I do in these situations. Capture the parent element prior to making the dialog and then, after the dialog is created, detach it from the DOM and re-append it back to the parent element.
var dlg = $('selector.dialog'),
dlgParent = dlg.parent();
dlgParent.append(dlg.dialog().detach());
This works especially well when dealing with ASPX forms (because any server-side tags that I need to get a postback value from must remain within the form).
So I have a button inside a list row that is used to delete the row from the page (calls ajax stuff to delete the object represented by the row, but that's not important for my question). The whole row is bound to a click event which would redirect to another page.
In other words, the containing row is click bound and the inner button is click bound, which is causing me problems since clicking the inner button also triggers the containing row click event (as it should).
I've tried binding a hover event for all delete buttons that unbinds the row click on mouseover, and rebinds it on mouseout, like this pseudocode below:
$('.delete-button').hover(
function() {
$('.list-row').unbind();
$('.delete-button').bind('click', function() { /* delete action */ });
},
function() {
$('.delete-button').unbind();
$('.list-row').bind('click', function() { /* list row action */ });
}
);
This isn't working very well, and I'm convinced there is a better way to approach it. Should I take the button out of the containing list-row? It's way easier to have it in there since my list row contains custom attributes that have data I need for the ajax calls and I can just var rid = $('.delete-button).parent().attr('row-id'); to get the data, but I'm not opposed to change :)
Thanks!
In your click event handler for the button, you need to call e.stopPropagation(). This will prevent the event from bubbling up the DOM tree. More info here: http://api.jquery.com/event.stopPropagation/
edit: you already accepted (thanks!), but maybe this code snippet would help explain some of the concepts better:
$('.list-row').click(function() {
/* list row action */
});
$('.delete-button').click(function(e) {
// die, bubbles, die
e.stopPropagation();
// if you also need to prevent the default behavior for the button itself,
// uncomment the following line:
// e.preventDefault();
// note that if you are doing both e.stopPropagation() AND e.preventDefault()
// you should just `return false;` at the end of the handler (which is jQuery-
// sugar for doing both of these at once)
/* delete action */
})
There's a few ways of approaching this. As #jmar777 has already said you may attach an altered event to the click handler on the button, stopping propagation.
If you want to do this with the same function as you're applying to the div then you can approach it as such:
if($(event.target).is("input")) {
event.stopPropagation();
}
Another approach is to actually not bind the click event to the button, for any time the browser supports clicks on the containing element. As you will always trigger that, then you don't actually need the button to handle it too! This does require you to handle IE6 etc a little differently from everything else though...
Let your handler function return false