I am starting a Web Application from scratch and I am using Backbone. So to start I was trying to have my whole app associated with a View, binding some anchors ( with internal links ) to a click event which gets the href and navigate to that, like this:
var App = new ( Backbone.View.extend( {
...
events : {
'click a[data-internal]' : function( event ) {
event.preventDefault();
Backbone.history.navigate( event.target.pathname, { trigger : true } );
}
},
start : function() {
Backbone.history.start( { pushState : true } );
}
}))({ el : document.body });
$( function() { App.start(); });
Then i have the following html:
<li>
<img src="assets/img/mail.png" alt="Mensagens" title="Mensagens">
</li>
My problem is, when the event is fired the event.target is always associated to the img child and not to the anchor. It doesnt seem to be something about bubbling. I have already tryed to use e.stopPropagation(). With no inner img it works well.
I don't understand why this happens. Can someone explain it to me?
Thanks a lot ;)
EDIT:
I solved the problem using event.currentTarget as ho.s suggested.
Related
The Twitter Bootstrap modal dialog has a set of events that can be used with callbacks.
Here is an example in jQuery:
$(modalID).on('hidden.bs.modal', function (e) {
console.log("hidden.bs.modal");
});
However, when I transcribe this method to JS the event 'hidden.bs.modal' does not work. Using 'click' does work:
document.querySelector(modalID).addEventListener('hidden.bs.modal', function(e) {
console.log("hidden.bs.modal");
}, false);
Are these Bootstrap events only useable with jQuery? Why?
Thanks,
Doug
The reasoning behind this is because Twitter Bootstrap uses that.$element.trigger('hidden.bs.modal')(line 997) to trigger it's events. In other words it uses .trigger.
Now jQuery keeps track of each element's event handlers (all .on or .bind or .click etc) using ._data. This is because there isn't any other way to get the event handlers that are bound (using .addEventListener) to an element. So the trigger method actually just get's the set event listener(s)/handler(s) from ._data(element, 'events') & ._data(element, 'handle') as an array and runs each of them.
handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
if ( handle ) {
handle.apply( cur, data );
}
(line 4548)
This means that no matter what context is, if an event is bound via .addEventListener it will not run using .trigger. Here's an example. On load only jquery will be logged (triggered by .trigger). If you click the a element though, both will run.
$('a')[0].addEventListener('click', function(){console.log('addlistener');}, false);
$('a').on('click', function(){
console.log('jquery');
});
$('a').trigger('click');
DEMO
Alternatively, you can trigger an event on an element in javascript using fireEvent(ie) & dispatchEvent(non-ie). I don't necessarily understand or know the reasoning of jQuery's .trigger not doing this, but they may or may not have one. After a little more research I've found that they don't do this because some older browsers only supported 1 event handler per event.
In general we haven't tried to implement functionality that only works on some browsers (and some events) but not all, since someone will immediately file a bug that it doesn't work right.
Although I do not recommend it, you can get around this with a minimal amount of changes to bootstraps code. You would just have to make sure that the function below is attached first (or you will have listeners firing twice).
$(modalID).on('hidden.bs.modal', function (e, triggered) {
var event; // The custom event that will be created
if(!triggered){
return false;
}
e.preventDefault();
e.stopImmediatePropagation();
if (document.createEvent) {
event = document.createEvent("HTMLEvents");
event.initEvent("hidden.bs.modal", true, true);
} else {
event = document.createEventObject();
event.eventType = "hidden.bs.modal";
}
event.eventName = "hidden.bs.modal";
if (document.createEvent) {
this.dispatchEvent(event);
} else {
this.fireEvent("on" + event.eventType, event);
}
});
Finally change the Twitter Bootstrap line from above to:
that.$element.trigger('hidden.bs.modal', true)
This is so you know its being triggered and not the event that you're firing yourself after. Please keep in mind I have not tried this code with the modal. Although it does work fine on the click demo below, it may or may not work as expected with the modal.
DEMO
Native Javascript Solution. Here is my way of doing it without JQuery.
//my code ----------------------------------------
export const ModalHiddenEventListener = (el, fn, owner) => {
const opts = {
attributeFilter: ['style']
},
mo = new MutationObserver(mutations => {
for (let mutation of mutations) {
if (mutation.type === 'attributes'
&& mutation.attributeName ==='style'
&& mutation.target.getAttribute('style') === 'display: none;') {
mo.disconnect();
fn({
owner: owner,
element: mutation.target
});
}
}
});
mo.observe(el, opts);
};
//your code with Bootstrap modal id='modal'-------
const el = document.getElementById('modal'),
onHide = e => {
console.log(`hidden.bs.modal`);
};
ModalHiddenEventListener(el, onHide, this);
Compatibility:
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe#Browser_compatibility
Hi I'm playing around with vue directives and I'm trying to prevent click event if the element is <a> or <button> tag. So my question is, is this possible to do using vue directive?
Html
<a
#click.stop.prevent="displayModal()"
v-noclick="test">
I'm a link
</a>
Vue directive
Vue.directive('noclick', {
bind( elem, binding, vnode ) {
let user = {'name': 'John Doe'}
if( user ) {
let hasAccess = false
if( !hasAccess ) {
if( elem.tagName === 'A' ) {
elem.addEventListener( 'click', ( event ) => {
//this should prevent the element on click event
//but not working
event.stopPropagation()
event.preventDefault()
event.stopImmediatePropagation()
return false
})
return true
}
}
}
}
}
Vue registers the #click event first before v-noclick where you add a second click event listener on that element. When the <a> is clicked, the displayModal() function will be executed first, then the listener in the v-noclick directive will be executed. If the listeners were registered in the opposite order, then it would probably work.
That aside though, it doesn't look like what you are trying to do should be done inside a custom directive. Instead you can do the same logic in the click handler itself:
<a #click="onClick">Foo</a>
onClick() {
if (whatever) {
this.displayModal()
}
}
i couldn't find a vue way to do this. but with js you can use conditional sequence like this
<a #click="cond && onClick"></a>
in this case if cond be equals to true then onClick will called
I'm prototyping a web app dealing with lots of views that are off screen until activated by an element currently on screen. Example:
<div class='current' data-view='home'>
<a href='#' data-target='menu'>View Menu</a>
</div>
<div data-view='menu'>
<a href='#' data-target='home'>Go back home</a>
</div>
Right now I've got the jQuery rigged to find the matching value of "data-target" to "data-view". When it finds the match, it toggles the class "current" between the two views.
Anyways! I'm hoping someone could help me figure out a good way to apply my enter and exit animations to the toggled elements. Here's what I tried:
$('[data-target]').on('click', function () {
var parentView = $(this).parents('[data-view]'),
currentView = $('.current');
function finishedAnimation() {
currentView.one('webkitAnimationEnd oanimationend msAnimationEnd animationend',
function() { currentView.removeClass(); });
};
if (parentView.data('view', 'home')) {
targetView.addClass('moveFromTop');
currentView.addClass('moveToBottom');
finishedAnimation();
}
else if (parentView.data('view', 'menu')) {
targetView.addClass('moveFromBottom');
currentView.addClass('moveToTop');
finishedAnimation();
}
$(this).parents('body').find('[data-view=' + $(this).data('target') + ']').addClass('current');
});
It works on the first click, but on the subsequent click to return home it fails to perform the animation correctly.
I've been digging around and switch cases look like a viable option (?). If anyone has guidance on a better approach it would be much appreciated.
Not exactly sure how your setup works, but I prefer to use an object ( as an interface ) for something like this:
function handleView( view ) {
views = {
home : function(){ /* Do stuff with view here */ },
menu : function(){}
}
views[view]()
}
$('[data-target]').on('click', function (e) {
e.preventDefault()
view = $(this).parent().data('view') /* This should return 'view name' */
handleView( view );
});
Remember that if you're adding a class that has a transition associated with it, you'll need to remove it too.
so targetView.addClass('moveFromTop'); needs targetView.removeClass('moveFromTop'); in order to properly toggle.
The Twitter Bootstrap modal dialog has a set of events that can be used with callbacks.
Here is an example in jQuery:
$(modalID).on('hidden.bs.modal', function (e) {
console.log("hidden.bs.modal");
});
However, when I transcribe this method to JS the event 'hidden.bs.modal' does not work. Using 'click' does work:
document.querySelector(modalID).addEventListener('hidden.bs.modal', function(e) {
console.log("hidden.bs.modal");
}, false);
Are these Bootstrap events only useable with jQuery? Why?
Thanks,
Doug
The reasoning behind this is because Twitter Bootstrap uses that.$element.trigger('hidden.bs.modal')(line 997) to trigger it's events. In other words it uses .trigger.
Now jQuery keeps track of each element's event handlers (all .on or .bind or .click etc) using ._data. This is because there isn't any other way to get the event handlers that are bound (using .addEventListener) to an element. So the trigger method actually just get's the set event listener(s)/handler(s) from ._data(element, 'events') & ._data(element, 'handle') as an array and runs each of them.
handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
if ( handle ) {
handle.apply( cur, data );
}
(line 4548)
This means that no matter what context is, if an event is bound via .addEventListener it will not run using .trigger. Here's an example. On load only jquery will be logged (triggered by .trigger). If you click the a element though, both will run.
$('a')[0].addEventListener('click', function(){console.log('addlistener');}, false);
$('a').on('click', function(){
console.log('jquery');
});
$('a').trigger('click');
DEMO
Alternatively, you can trigger an event on an element in javascript using fireEvent(ie) & dispatchEvent(non-ie). I don't necessarily understand or know the reasoning of jQuery's .trigger not doing this, but they may or may not have one. After a little more research I've found that they don't do this because some older browsers only supported 1 event handler per event.
In general we haven't tried to implement functionality that only works on some browsers (and some events) but not all, since someone will immediately file a bug that it doesn't work right.
Although I do not recommend it, you can get around this with a minimal amount of changes to bootstraps code. You would just have to make sure that the function below is attached first (or you will have listeners firing twice).
$(modalID).on('hidden.bs.modal', function (e, triggered) {
var event; // The custom event that will be created
if(!triggered){
return false;
}
e.preventDefault();
e.stopImmediatePropagation();
if (document.createEvent) {
event = document.createEvent("HTMLEvents");
event.initEvent("hidden.bs.modal", true, true);
} else {
event = document.createEventObject();
event.eventType = "hidden.bs.modal";
}
event.eventName = "hidden.bs.modal";
if (document.createEvent) {
this.dispatchEvent(event);
} else {
this.fireEvent("on" + event.eventType, event);
}
});
Finally change the Twitter Bootstrap line from above to:
that.$element.trigger('hidden.bs.modal', true)
This is so you know its being triggered and not the event that you're firing yourself after. Please keep in mind I have not tried this code with the modal. Although it does work fine on the click demo below, it may or may not work as expected with the modal.
DEMO
Native Javascript Solution. Here is my way of doing it without JQuery.
//my code ----------------------------------------
export const ModalHiddenEventListener = (el, fn, owner) => {
const opts = {
attributeFilter: ['style']
},
mo = new MutationObserver(mutations => {
for (let mutation of mutations) {
if (mutation.type === 'attributes'
&& mutation.attributeName ==='style'
&& mutation.target.getAttribute('style') === 'display: none;') {
mo.disconnect();
fn({
owner: owner,
element: mutation.target
});
}
}
});
mo.observe(el, opts);
};
//your code with Bootstrap modal id='modal'-------
const el = document.getElementById('modal'),
onHide = e => {
console.log(`hidden.bs.modal`);
};
ModalHiddenEventListener(el, onHide, this);
Compatibility:
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe#Browser_compatibility
I'm using this click method to reveal additional classes inside a .tile
var $tiles = $('.tile');
$tiles.on('click', function(){
var $this = $(this);
$this.toggleClass('large'); // change layout of tile
$this.children('.details').toggle(); // display additional info
})
Since a .tile may contain links, I'd like to limit the click functionality. I don't want the function to trigger when a link inside .tile is clicked.
Edit: While #antyrant's answer is working for normal links, I noticed in my example the problem persists with on of the links that uses fancyBox.
This is what my HTML looks like:
<div class="tile">
<ul class="details">
<li><a class="download"> href="#">Download</a></li>
<li><a class="fancybox-media" href="#">Video</a></li>
</ul>
</div>
Clicking the download link works fine, but the fancyBox will not work!
Edit 2: See this jsFiddle for a working example.
You need to stop events propagation on child elements, for example:
$tiles.find( 'a' ).click( function( e ) {
e.stopPropagation();
});
UPDATE:
If this can be done as you use some plugins for example you can check if event target has your base class: for example:
$('.tile').on('click', function ( e ) {
if( !$( e.target ).hasClass( 'tile' ) ) {
return true;
}
//...
see jsFiddle demo.
stopPropagation() will stop your child event from bubbling up, and being caught by the listener on the parent element.
var $tiles = $('.tile');
$tiles.on('click', function () {
console.log('clicked');
})
$('a', $tiles).on('click', function (e) {
e.stopPropagation(); //Prevents event from bubbling up
})
#antyrat is correct, stopping propagation on the anchors will achieve your goal, but that requires binding another event that isn't really necessary. Another option would be to look at the target on the event passed in to your handler.
$tiles.on( 'click', function( e ) {
if( !$( e.target ).is( 'a' ) ) { // if the element clicked is not an anchor.
// do stuff
}
});
EDIT: Here's a fiddle demonstrating this working using the updated html provided above.