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 thewindow.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 es 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.
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 thewindow.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 es 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.
Share Improve this question edited Aug 4, 2013 at 19:11 I. J. Kennedy 25.9k17 gold badges65 silver badges88 bronze badges asked Jul 3, 2013 at 15:17 BarneyBarney 16.5k5 gold badges65 silver badges80 bronze badges 5- Could you give an example of how a user might use the system you're describing? – Samuel Reid Commented Jul 3, 2013 at 15:31
- @SamuelReid I'm working on a rich single-page web app with a strong focus on progressive enhancement: all interactions and navigations that are conceptually feasible for user agents without Javascript are handled with form elements, and (most often) links — a lot of the time hashes are used to navigate to or reveal elements within the page. But when Javascript is applied, I would often want to interrupt those events and replace native behaviour. – Barney Commented Jul 3, 2013 at 15:33
- @SamuelReid for a specific example: there is something like a 'footnotes viewer' that displays extensive citations. Native behaviour is to simply jump to the citation in question, but enhanced behaviour opens up a modal interface to better interact with citations and source material references holistically. I want people to be able to share permalinks to footnotes, but if JS is available I still want the 'footnotes viewer' where possible. – Barney Commented Jul 3, 2013 at 15:36
- So you need it so that if scenario 2 does not cancel the default event, scenario 3 will run? – Samuel Reid Commented Jul 3, 2013 at 15:51
- @SamuelReid no — I'm saying that a hashchange resulting from a click should not trigger another target event, because in holistic terms that targeting event has already been captured. – Barney Commented Jul 3, 2013 at 16:19
3 Answers
Reset to default 5 +200If 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/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.