Capture only hashchange events not resulting from anchor clicks - javascript

I am trying to use Javascript to emulate the CSS :target pseudo-class so as to capture all events that result in an element on page being targeted. I've identified 3 trigger events:
window.location.hash already targets an element of the same ID on initialisation
An anchor targeting the element is clicked
The hashchange event is fired independently of the above (for example via the window.history API)
Scenario 2 is important as a distinct case since I would want to invoke the click event's preventDefault. The simplified code for this scenario follows:
$('body').on('click', 'a[href*=#]', function filterTarget(clickEvent){
$(this.hash).trigger('target', [clickEvent]);
});
The problem comes when trying to implement scenario 3:
$(window).on('hashchange', function filterTarget(hashChangeEvent){
$(this.hash).trigger('target', [hashChangeEvent]);
});
If a target handler doesn't cancel the native behaviour for scenario 2, it will be triggered again when the native behaviour causes the resulting hashchange event. How can I filter out these edge cases?
POST-SOLUTION EDIT:
roasted's answer held the key — handle a namespaced hashchange event, then unbind and rebind the handler based on logic handled inside the click handler and its preventDefault. I wrote up the full plugin here.

If i understand it, you don't want the hashchange event to be fired if an anchor tag is clicked. You could then set your logic using namespaced events:
DEMO
$('body').on('click', 'a[href*=#]', function (clickEvent) {
filterTarget(clickEvent,this);
$(window).off('hashchange.filter').on('hashchange.tmp', function () {
$(this).off('hashchange.tmp').on('hashchange.filter', filterTarget);
});
});
$(window).on('hashchange.filter', filterTarget);
function filterTarget(event,elem) {
$(elem?elem.hash:window.location.hash).trigger('target', [event]);
//you could filter depending event.type
alert(event.type + '::'+ (elem?elem.hash:window.location.hash));
}

if the click is setting the hash with the fragment anyway, just throw away duplicates in the hash change event:
onhashchange=function(e){
if(e.newURL == e.oldURL ){return; }
//do your normal hashchange event stuff below:
};
ref: https://developer.mozilla.org/en-US/docs/Web/API/window.onhashchange
this fixes cascade issues no matter what invoked the change.

Seems like you could use mousedown instead of click, if you're going to be calling preventDefault on it. Then presumably the hashchange would not be triggered.

Related

Stop propagation doesn't work

I have the below JQuery eventhandler. I want to stop all navigations on a web page.
$(document).click(function(event) {
event.stopPropagation();
event.preventDefault();
event.cancelBubble = true;
event.stopImmediatePropagation();
$(document).css('border-color','');
$(document).css('background-color','');
$(event.target).css('border-color','yellow');
$(event.target).css('background-color','#6BFF70');
return false;
});
When I use this on Facebook Login page, it stops all navigations. But in Google home page, "I'm Feeling Lucky" button still navigates to next page. How do I avoid it?
I'm using JavaFX browser by the way. It is similar to Safari browser.
If I load the Google search page, and execute this at the console:
document.body.addEventListener(
"click",
function (ev) { ev.stopPropagation(); ev.preventDefault(); },
true);
then I cannot click the "I'm Feeling Lucky" button anymore. The key is to use the third parameter and set it to true. Here is what MDN [says] about it:
useCapture Optional
If true, useCapture indicates that the user wishes to initiate capture. After initiating capture, all events of the specified type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree.
(Emphasis added.)
What you tried to do does not work because your event handler is on document, and thus will be called after any event handlers on the children of the document. So your handler cannot prevent anything.
With useCapture set to true, you can operate on the event before it gets a chance to be passed to the child element. I do not know of a way to have jQuery's event handlers work in the way you get with useCapture. Barmar's answer here says you can't use jQuery to set such handler. I'm inclined to believe him.
99.99% of webpages won't be able to have their navigation stopped by stopping event propagation for the reason I commented (you can't stop the event before it triggers all handlers for the initial target of the event). If preventing navigation is all you are interested in, I recommend using the window.onbeforeunload event, which is made for this exact situation.
Here is an example: http://jsfiddle.net/ejreseuu/
HTML:
google
JS:
window.onbeforeunload = function() {
return "Are you sure?"
}
There is no way to not have a confirmation box that I know of, as code that locks the user out of navigating away no matter what they do is generally malicious.
preventDefault() should not work in this case, cause Google relied on custom event listeners to handle click events on this button. While preventDefault()
prevents browser's default behavior.
For example, if this button was of type="submit", preventing default on click event would prevent browser's default behavior, which is submitting a form. But in this case click is handled by eventListeners added to the button itself. preventDefault() won't affect catching an event by them. Nor stopPropagation(), because it stops propagation of event to higher levels of DOM, while other eventListeners on the same level (button in our case) still get the event. stopImmediatePropagation() could work in theory, but only if your eventListener was added before google's.
So the easiest way to stop propagation is to stop an event before it reaches button node, and that's on capture phase, because button is the lowest element in the hierarchy. This can be done by passing true argument while adding eventListener
document.body.addEventListener("click", function (event) {
event.stopPropagation();
}, true);
This way event will be stopped before bubble phase, and so before it reaches eventListeners added to the button. More on capture and bubble phases here
Note that preventDefault() is not needed in this case. Actually, this button's event listeners are to prevent default themselves. Here are those eventListeners, for click and keyup respectively:
d = function(a) {
c.Xa.search(c.yc(), b);
return s_1vb(a)
}
function(a) {
13 != a.keyCode && 32 != a.keyCode || d(a)
}
note call to s_1vb, here is its sourse:
s_1vb.toString();
/*"function (a){
a&&(a.preventDefault&&a.preventDefault(),a.returnValue=!1);
return!1
}"*/
Basically its a function that take an event and do everything possible to prevent browser's default behavior
By the way, default behavior can be canceled on any stage of event flow (se Events Specification), including the very last stage, when it reached document. Only after it passed "through" all eventListeners uncanceled, browser should execute its default behavior. So attaching your listener to document was not the reason preventDefault() didn't work, it was because it was the wrong guy for the job :)
Try this:
$('body').click(function(event) {
event.stopPropagation();
event.preventDefault();
event.cancelBubble = true;
event.stopImmediatePropagation();
$(document).css('border-color','');
$(document).css('background-color','');
$(event.target).css('border-color','yellow');
$(event.target).css('background-color','#6BFF70');
return false;
});
Try to bind not only to click event, but as well on mousedown event.
Try this css:
body * {
pointer-events: none;
}
or in jQuery:
$("body *").css("pointer-events", "none");
Try declaring a new window event and then stopping the propagation from there:
var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation)
{
e.stopPropagation();
}
Note that Google uses jsaction="..." instead of onclick="...". Try to use it's unbind method on the specified button.
Also you can use dynamic attachment, like:
$(document).on('click', '*', function
Or throw new Error()(just as a dirty hack)

Manually click a child link (and navigate) when clicking parent [duplicate]

Recently I found jQuery cannot trigger the native click event on an anchor tag when I'm clicking on other elements, the example below won't work:
html
<a class="js-a1" href="new.html" target="_blank">this is a link</a>
<a class="js-a2" href="another.html" target="_blank">this is another link</a>
javascript
$('.js-a1').click(function () {
$('.js-a2').click();
return false;
});
And here is the jsfiddle - 1. Click on the first link won't trigger native click on the second one.
After some searches, I found a solution and an explanation.
Solution
Use the native DOM element.
$('.js-a1').click(function () {
$('.js-a2').get(0).click();
return false;
});
And here is the jsfiddle - 2.
Explanation
I found a post on Learn jQuery: Triggering Event Handlers. It told me:
The .trigger() function cannot be used to mimic native browser events, such as clicking on a file input box or an anchor tag. This is because, there is no event handler attached using jQuery's event system that corresponds to these events.
Question
So here comes my question:
How to understand 'there is no event handler attached using jQuery's event system that corresponds to these events'?
Why is there not such corresponding event handler?
EDIT
I update my jsfiddles, it seems there's and error on the class name.
there is no event handler attached using jQuery's event system that corresponds to these events
This means, at this point of the learning material, no jQuery event handlers has been attached to these elements using .click(function() {} or .bind('click', function () {}), etc.
The no-argument .click() is used to trigger (.trigger('click')) a "click" event from jQuery's perspective, which will execute all "click" event handlers registered by jQuery using .click, .bind, .on, etc. This pseudo event won't be sent to the browser.
.trigger()
Execute all handlers and behaviors attached to the matched elements for the given event type.
Check the updated jsFiddle example, click on the two links to see the difference. Hope it helps.
First of all you need to prevent the default behaviour of link
$('.js-a1').click(function (e) {
e.preventDefault();
$('.js-a2').get(0).click();
return false;
});
And to trigger the click event you can also use .trigger('click') better way
And the event handler is used like this:
$(document).on('click', '.js-a1',function(){//code in here});
// here now .js-a1 is event handler
i think you forgot to read documentation.
Document says :
// Triggering a native browser event using the simulate plugin
$( ".js-a2" ).simulate( "click" );
Old question, but here's a nifty and simple solution:
You can basically "register" a native JS event with jQuery by assigning the DOM element's onEvent handler to be the native event. Ideally, we would check first to ensure the onEvent handler has not already been set.
For example, 'register' the native JS click event so it will be triggered by jQuery:
$('.js-a1').click(function (e) {
$('.js-a2').click();
e.preventDefault();
});
var trigger_element = $('.js-a2')[0]; // native DOM element
if (!trigger_element.onclick) {
trigger_element.onclick = trigger_element.click;
}
Here is a fiddle: http://jsfiddle.net/f9vkd/162/
You have to use $("selector").trigger('click')

Access all event listeners in Javascript

I want to do something like this:
function('string', function2(){})
where I leave the to user to write what he wants in the string parameter and than execute function2.
The catch is here: string is an event listener. When the user writes click, I want to call onClick(), when the user writes mouse I want to call onMouseOver and so on.
I have in mind doing something with case, but how can I access all event listeners?
You should use addEventListener.
element.addEventListener("string", function() {}, false);
However, in the case of IE <= 8, you will need to use attachEvent as it does not follow the standard:
element.attachEvent("string", function() {});
Finally, as kybernetikos mentions in his comment, you can then use a simple dictionary to map mouse to mouseover.
If you wish to fire events, you should use dispatchEvent.
If you add the event listeners using the old model (i.e. elem.onclick = function(){ /* */ };), you can use
elem['on' + event]();
Keep in mind that this only fires the event listeners, but doesn't create an event (e.g. it won't bubble).
If you won't to create a event, which fires event listeners added using addEventlistener, and bubbles, and does all things a real event does, you must
Create your event using event constructors: Event or CustomEvent
Fire it with dispatchEvent
See MDN page for more information and examples.
you can use .trigger to do this. Check out this example in jsfiddle. type "dblclick" in the input box.
http://jsfiddle.net/jspatel/Suj4H/1/
<input id="writehere"> </input>
$('#writehere').dblclick(function() {
alert ('dblclick');
});
$('#writehere').bind('keypress', function(e) {
if(e.keyCode==13){
$(this).trigger( $(this).val() );
}
});

jQuery programmatically trigger events

What all events can be triggered programmatically using jQuery? Also is there any important differences to be remembered when one is doing event triggering using jQuery Vs a natural way of it being triggered?
Every event can be programmatically fired, just use the callback-less version of it.
Example:
$('#button').click(function() { alert('event hanlder'); });
$('#button').click(); // generate the event
About your second question, there should be no difference between the native and jQuery event handlers.
One thing that is neat though is that jQuery binds this to the element that received the event, inside the callback (this doesn't happen in native event handlers):
$('#button').click(function() { alert(this); }); // here 'this' == document.getElementById('button');
Warning: the element referenced by this is not "jQuery augmented". If you want to traverse or modify it with jQuery goodness you'll have to do something like var $this = $(this);
You should know the differences between trigger and triggerHandler in jQuery.
trigger
trigger attempts to replicate the natural event as best as it can. The event handler for the event being triggered get's executed, but the default browser actions will not always be replicated exactly. For example $('a#link).trigger('click'); will execute the javascript function bound to the links click event handler, but will not redirect the browser to the href of the anchor, like a normal click would. EX: http://jsfiddle.net/AxFkD/
All the short forms of the trigger call behave exactly like trigger IE. click(), mouseup(), keydown(), etc
triggerHandler
triggerHandler prevents bubbling up ( EX. http://jsfiddle.net/LmqsS/ ), it avoids default browser behaviour and just executes the events callback, and it returns the return value of the event handler instead of a jQUery object for chaining.
You should also be aware that trigger affects all elements matched by a selector, but triggerHandler only affects the first one EX: http://jsfiddle.net/jvnyS/
You can trigger any event programmatically. But most of the events cannot be simulated as the natural event using programmatic triggers.
//to trigger a click event on a button
$("buttonSelector").trigger("click");
First, for obvious reasons, you cannot trigger the ready event.
That said, events raised by trigger() behave the same way as if they were triggered by the user. In particular, the event handlers are called in the same order.
The only difference I know of is that triggered events did not bubble up the DOM tree in older versions of jQuery (that behavior was fixed in version 1.3).

Jquery remove and add back click event

Is it possible to remove than add back click event to specific element? i.e
I have a $("#elem").click(function{//some behaviour});, $(".elem").click(function{//some behaviour});(there are more than 1 element) while my other function getJson is executing I'd like to remove the click event from the #elem, and add it again onsuccess from getJson function, but preserve both mouseenter and mouseleave events the whole time?
Or maybe create overlay to prevent clicking like in modal windows? is that better idea?
edit :
I've seen some really good answers, but there is one detail that I omitted not on purpose. There are more than one element, and I call the click function on the className not on elementId as I stated in the original question
Rather than using unbind(), which means you'll have to rebind the same event handler later, you can use jQuery's data() facility with the ajaxStart and ajaxStop events to have your elements ignore click events during all AJAX requests:
$(".elem").click(function() {
if (!$(this).data("ajaxRequestPending")) {
// some behaviour
}
}).ajaxStart(function() {
$(this).data("ajaxRequestPending", true);
}).ajaxStop(function() {
$(this).removeData("ajaxRequestPending");
});
EDIT: This answer is also id-to-class-proof (see questioner's edit), since everything matching the selector will handle the AJAX events the right way. That's the main selling point of jQuery, and it shows.
You are looking for .unbind(). Pass it 'click' and it will destroy the click event.
I would put it just before your getJSON and re-bind the click event inside the success handler of your ajax call.
You have to do some additional scripting. There is no callback for that. Take a look over here: jQuery - How can I temporarily disable the onclick event listener after the event has been fired?
Rather than unbinding/binding the click event, you could check the state of another variable to see if it should do the action.
var MyObject = {
requestActive = false;
};
function MyJsonFunction() {
// when requesting
MyObject.requestActive = true;
//...
// when request over
MyObject.requestActive = false;
}
$("#elem").click(function{
if (MyObject.requestActive == true) {
//do something
}
});

Categories