Redirect an event to an inner element - javascript

I have a special scenario where I need to capture key events on a higher level, and redirect them to a lower level. I'm trying to write code like this, but actually it seems like jQuery is dumbly using the exact same event object, so target is not changed, so Maximum callstack size exceeded...
$("#outer").on("keydown", function(evt) {
if (evt.target !== $("#inner")[0] && !$.contains($("#inner")[0], evt.target)) {
$("#inner").trigger(evt);
}
});
With a sample markup
<div id="#outer">
<div id="#inner">
</div>
</div>
What is the proper way to redirect an event to another element? I could of course manually "hack" the target, but is it safe?
I'm working on a plugin which dynamically shows a menu as a popup or as a modal based on screen size. If I have a modal, I need this key redirection. The consumer should not know about wether it's a popup or a modal. I'm using bootstrap's modal plugin, which captures key events because of a "focus-lock thing", so the only way I can make it transparent is that I redirect these events to my real menu.

You could attach the event handler onto the outer and specify a selector to use to match the event:
$('#outer').on('keydown', '#inner', event => {
/* this is executed when #inner is clicked */
});
If you really want to use trigger, you could put it in a setTimeout:
$('#outer').on('keydown', event => {
if (yourCondition) {
setTimeout(() => $('#inner').trigger(event), 0);
}
});

The event will bubble up and you're right, it's the same event. This isn't a jQuery thing but actually just how javascript works. You may want to set a listener on the child element, and use preventDefault();
$("#outer").on("keydown", doSomeThing);
$("#inner").on("keydown", doSomeThingElse);
function doSomeThing(e) {
}
function doSomeThingElse(e) {
e.preventDefault();
}
this will allow you to separate your listeners into distinct functions.

Thank you all for the answers and comments. A few of you suggested preventing propagation of the event at the #inner div. I need to avoid this, I need a way where any external consumer will see this event just as it was triggered by the #inner element for real.
In the meantime I digged into jQuery source and found this line in trigger.
if ( !event.target ) {
event.target = elem;
}
So when jQuery initializes the event to trigger, it only assignes the element to the target if the target is not yet specified. sure they have some good reasons for this behavior, which I cannot see at this moment.
So the best thing I could come up with is a function like this.
function redirectEvent(currentTarget, evt, newTarget) {
if (currentTarget !== newTarget && !$.contains(currentTarget, newTarget)) {
evt = $.Event(evt.type, evt);
delete evt.target;
$(newTarget).trigger(evt);
}
}
As far as first tests go, I can't see any side-effect or drawback.

Related

Global onclick handler vs. per-element event handlers

Consider this example:
You're creating a website that uses client-side rendering.
When the user clicks a link, you want to disable the browser's navigation and render the new page yourself.
Perhaps you also have some html like this, and you want to call upvote_post(id) every time the button is clicked:
<post-element data-id=12345>
...
<upvote-button></upvote-button>
</post-element>
The common way to handle both of these cases would be to add a click event handler on every <a>, <upvote-button>, etc. element that you generate.
But wouldn't it be better to just add an event handler on the document itself?
document.addEventListener('click', function(e) {
for (let elem of e.path) {
if (elem instanceof HTMLAnchorElement && elem.origin==window.location.origin) {
e.preventDefault()
render_page(elem.href)
break
} else if (elem.tagName=='UPVOTE-BUTTON') {
let post = elem.closest('post-element')
upvote_post(post.dataset.id)
break
}
}
})
Of course, it feels wrong to bypass the builtin event handler system, but I can't think of any disadvantages here.
You can still add event handlers to specific elements if you need to, but for common actions, this seems like a better solution.

Calling an Onclick() function without clicking?

A websites Button isn't rendering on my browser. I know it's there, when I'm in the view source I still see the button, and the onclick function. Can I still call this function by passing the url the function? Or in the console?
Even if it's not an onclick, is it possible to call functions by either URL or Console?
You can click any DOM element by selecting it and calling the click() function.
document.getElementById("yourElementId").click();
Works with Jquery as well.
$(selector).click();
To accomplish this, you can use the following line:
document.getElementById("element_id").click()
According to MDN, this simulates a full click on the element, including all it entails:
When click is used with elements that support it (e.g. one of the types listed above), it also fires the element's click event which will bubble up to elements higher up the document tree (or event chain) and fire their click events too. However, bubbling of a click event will not cause an element to initiate navigation as if a real mouse-click had been received.
Alternatively, you can use jQuery to accomplish the same thing:
$("#trigger").click();
Here is the simplest way:
function fireClick(node){
if ( document.createEvent ) {
var evt = document.createEvent('MouseEvents');
evt.initEvent('click', true, false);
node.dispatchEvent(evt);
} else if( document.createEventObject ) {
node.fireEvent('onclick') ;
} else if (typeof node.onclick == 'function' ) {
node.onclick();
}
}
So you can trigger the click event for an element like below:
fireClick(document.getElementById('id'));
Reference from here.

Closure event delegation - event listener on DOM parent that covers children/descendants of a given class

In jQuery, you can do the following:
$('#j_unoffered').on('click', '.icon_del', function () {...
This puts one handler on the element j_unoffered that fires if any descendant element with class icon_del is clicked. It applies, furthermore, to any subsequently created icon_del element.
I can get this working fine in Closure where the click is on the element itself.
goog.events.listen(
goog.dom.getElement('j_unoffered'),
goog.events.EventType.CLICK,
function(e) {...
How can I specify a parent event target in Closure that works for its children/descendants in the same way as the jQuery example?
I'm assuming I need to use setParentEventTarget somehow, but I'm not sure how to implement it for DOM events. Most of the documentation I've found pertains to custom dispatch events.
-- UPDATE --
I'm wondering if there is anything wrong with this rather simple solution:
goog.events.listen(
goog.dom.getElement('j_unoffered'),
goog.events.EventType.CLICK,
function(e) {
if (e.target.className.indexOf('icon_del') !== -1) {...
It still leaves this bound to the parent, but e.target allows a work-around. The fifth argument in listen (opt_handler) allows you to bind this to something else, so I guess that's an avenue, too.
I don't know about such possibility too, so I suggest other piece of code:
var addHandler = function(containerSelector, eventType, nestSelector, handler) {
var parent = goog.isString(containerSelector) ?
document.querySelector(containerSelector) :
containerSelector;
return goog.events.listen(
parent,
eventType,
function(e) {
var children = parent.querySelectorAll(nestSelector);
var needChild = goog.array.find(children, function(child) {
return goog.dom.contains(child, e.target);
});
if (needChild)
handler.call(needChild, e);
});
});
Usage:
addHandler('#elem', goog.events.EventType.CLICK, '.sub-class', function(e) {
console.log(e.target);
});
Update:
If you will use this e.target.className.indexOf('icon_del') there will be possibility to miss the right events. Consider a container div with id = container, it has couple of divs with class innerContainer, and each of them contains couple of divs with class = finalDiv. And consider you will add event handler with your code above, which will check e.target for innerContainer class. The problem is when user will click on finalDiv your handler will be called, but the event target will be finalDiv, which is not innerContainer, but contained by it. Your code will miss it, but it shouldn't. My code checks if e.target has nested class or contained by it, so you will not miss such events.
opt_handler can't really help you either, because there might be many nested elements you want to hanlde (which of them will you pass here? maybe all, but not that helpful, you can get them in event handler whenever you want), moreover they can be added dynamically after, so when you add handler you could not know about them.
In conclusion, I think doing such a job in an event handler is justified and most efficient.
What you are referring to is called event delegation
It seems that this is not possible (out of the box) with Google Closure Library; So my recommandation is to use jQuery or another similar event handling library that offers this functionality. If this is not possible or if you wanna do it by hand here's one possible approach (NOTE: this is not for production use)
var delegateClick = function(containerId, childrenClass, handler){
goog.events.listen(goog.dom.getElement(containerId), goog.events.EventType.CLICK, function(event){
var target = event.target;
//console.log(event);
while(target){
if ( target.className && target.className.split(" ").indexOf(childrenClass)!== -1) {
break;
}
target = target.parentNode;
}
if(target){
//handle event if still have target
handler.call(target, event);
}
});
}
//then use it, try this here: http://closure-library.googlecode.com/git/closure/goog/demos/index.html
//..select the nav context
delegateClick( 'demo-list' ,'goog-tree-icon', function(event){console.log(event);})
Here's a more in depth analysis of event delegation
Again, you should use a proven library for this, here are some options: jQuery, Zepto, Bean

How to stop event propagation from within an anchor's href attribute without using onclick or onmousedown

Due to restrictions, even though it is something i avoid altogether, in a certain situation i have to use the javascript: syntax in a href attribute of an achor tag.
(EXPLANATION: In my CMS i use a rich text editor to allow the user to make changes to text elements, including links. In some cases specific javascript: calls are required and i banned onclick completely from the link editing features (to simplify the process for the user). However, as one of the links appears within a block that reacts to an onclick event, the thing double-fires)
Like this:
My problem is that this link is inside a container that already reacts to an onclick event. Therefore i wanted to pass the event object along to the doSomething() method, so that i could then use jQuery's
event.stopPropagation()
method.
Unfortunately however, it seems that passing the event object along
does not seem to work at all. Safari won't say anything while Firefox will report ReferenceError: event is not defined
I assume that this is the case because href="" is not a script-initiating attribute (such as onclick). The problem is that in this situation i won't be able to access the tag beyond what i already do.
Therefore i either need
1.) A way to pass the event object to the doSomething() function from within the href attribute
or
2.) A way to stop the event propagation right in that anchor (after its clicked) by other means.
Thank You for any constructive input!
You cannot stop event propagation from the href attribute because:
When the href code executes, it is not an event. It just executes that code, similar to the "location hack". Like entering javascript:doSomething() in the browser's address bar.
The href code executes after the events fire on the link -- including bubbling.
You can see that behavior in this jsFiddle. Note that mouseup, mousedown, and click all fire both for the link, and on the container when the link is clicked, before the href code executes.
If there are event listeners that you want to block, you'll have to find another way.
But, if you can append javascript to the document you can block the href using preventDefault().
For example:
jQuery, before version 1.7:
$("#container a").bind ("mousedown mouseup click", function (e) {
e.preventDefault();
} );
jQuery 1.7 and later:
$("#container a").on ("mousedown mouseup click", function (e) {
e.preventDefault();
} );
or (better):
$("#container").on ("mousedown mouseup click", "a", function (e) {
e.preventDefault();
} );
You can see this last version live at jsFiddle.
If you cannot alter the link itself (to use onclick) then your only option is to alter the onclick handler of the container.
Can you do something like
function containerClickHandler(e) {
e = e || event;
var el = e.target || e.srcElement;
if (el.nodeName === 'A' && someOtherMatchChecks) {
// eat event
}
else {
// process event
}
}
Well, this is an old question, but in my particular case I did find a hack around it, but it might only apply to a subset of situations. I have a div that has an onclick. But if an inside that div is clicked, I don't want that div's onclick to fire. Here is what I do:
function myOnClick () {
// loop over all <a>'s, and test if they are hovered over right now.
var allLinks = document.links;
var dont = 0;
for (var i = 0, n = allLinks.length; i < n; i++) {
// pure javascript test to see if element is hovered.
if(allLinks[i].parentElement.querySelector(":hover") === allLinks[i]) {dont = 1; }
};
if(dont)return;
// your stuff here, only fires when dont is false.
}
I learned about the queryselector trick here: https://stackoverflow.com/a/14800287/2295722
I don't know if there is a way to get the arguments if you write your javascript in href attribute. But you can get it as following in onclick, but as you say this isn't the best practice:
<a onclick="console.log(arguments)">your link</a>
in arguments array you'll get your event object.
here is a demo for you:
http://jsfiddle.net/tEw5J/1/

Properly bind Javascript events

I am looking for the most proper and efficient way to bind Javascript events; particularly the onload event (I would like the event to occur after both the page AND all elements such as images are loaded). I know there are simple ways to do this in jQuery but I would like the more efficient raw Javascript method.
There are two different ways to do it. Only one will work; which one depends on the browser. Here's a utility method that uses both:
function bindEvent(element, type, handler) {
if(element.addEventListener) {
element.addEventListener(type, handler, false);
} else {
element.attachEvent('on'+type, handler);
}
}
In your case:
bindEvent(window, 'load', function() {
// all elements such as images are loaded here
});
I know you did only ask about how to bind events. But Ooooo boy the fun doesn't end there. There's a lot more to getting this right cross-browser than just the initial binding.
#d.'s answer will suffice just fine for the specific case of the load event of window you're looking for. But it may give novice readers of your code a false sense of "getting it right". Just because you bound the event doesn't mean you took care to normalize it. You may be better of just fixing window.onload:
window.onload = (function(onload) {
return function(event) {
onload && onload(event);
// now do your thing here
}
}(window.onload))
But for the general case of event binding #d.'s answer is so far from satisfying as to be frustrating. About the only thing it does right is use feature-detection as opposed to browser detection.
Not to go on too much of a rant here but JavaScript event binding is probably the #1 reason to go with a JavaScript library. I don't care which one it is, other people have fixed this problem over and over and over again. Here're the issues in a home-brewed implementation once inside your handler function:
How do I get a hold of the event object itself?
How do I prevent the default action of the event (eg clicking on a link but not navigating)
Why does this point to the window object all the time?
(Re mouse events) What are the x/y coords of the event?
Which mouse button triggered the event?
Was it a Ctrl-click or just a regular click?
What element was the event actually triggered on?
What element is the event going to? (ie relatedTarget, say for blur)
How do I cancel the bubbling of the event up through its parent DOM?
(Re event bubbling) what element is actually receiving the event now? (ie currentTarget)
Why can't I get the freaking char code from this keydown event?
Why is my page leaking memory when I add all these event handlers?? Argh!
Why can't I Unbind this anonymous function I bound earlier?
And the list goes on...
The only good reason to roll your own in these days is for learning. And that's fine. Still, read PPK's Intro to browser events. Look at the jQuery source. Look at the Prototype source. You won't regret it.
Something like that
window.onload = (function(onload) {
return function(event) {
onload && onload(event);
$(".loading-overlay .spinner").fadeOut(300),
$(".loading-overlay").fadeOut(300);
$("body").css({
overflow: "auto",
height: "auto",
position: "relative"
})
}
}(window.onload));
window.onload = function() {
// ...
};

Categories