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
Related
Currently dojo uses on method to connect event to handler.
btn = new Button();
btn.on('click', function () {console.log('do something');});
this will call the attached function when the button gets clicked.
however, according to the documents, removing existing handlers should be done in the following way
handler = btn.on('click', function () {console.log('do something');});
handler.remove();
this is not the way I want to remove event handler.
I do not store the handler reference anywhere. But I want to add a new 'click' event by doing
btn.on('click', function () {console.log('do something different');});
so that it replaces the existing 'click' event handler and add a new one.
Is there any way to achieve what I want?
Thanks!
That's not possible, the framework tells you to do it in the way by creating a reference to the event handler. This is similar to how other frameworks like jQuery work.
jQuery has of course a mechanism to remove all event handlers by using the off() function, but that's not available in Dojo either. Like Chris Hayes suggested in the comments, you can implement such a feature by yourself, either by wrapping it inside another module, or by using aspects on the dojo/on module.
For example, you can wrap it inside a new module:
// Saving the event handlers
var on2 = function(dom, event, callback) {
on2.handlers = [];
if (on2.handlers[event] === undefined) {
on2.handlers[event] = [];
}
var handler = on(dom, event, callback);
on2.handlers[event].push({
node: dom,
handler: handler
});
return handler;
};
// Off functionality
lang.mixin(on2, on, {
off: function(dom, event) {
if (this.handlers[event] !== undefined) {
array.forEach(this.handlers[event], function(handler) {
if (handler.node === dom) {
handler.handler.remove();
}
});
}
}
});
And then you can use it:
on2(dom.byId("test"), "click", function() {
console.log("test 1 2 3"); // Old event handler
});
on2.off(dom.byId("test"), "click"); // Remove old event handlers
on2(dom.byId("test"), "click", function() {
console.log("test 4 5 6"); // New event handler
});
This should work fine, as you can see in this fiddle: http://jsfiddle.net/X7H3F/
btn = new Button();
btn.attr('id','myButton');
query("#myButton").on('click', function () {console.log('do something');});
Do the same thing when you want to replace your handler. Like,
query("#myButton").on('click', function () {console.log('do something different');});
Hope that helps :)
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 have a MooTools script (please dont ask why..) where elements are added a mouseenter event. In jQuery, I open/show those elements within a fancybox. When it pops up, the mouseenter event wont get fired in the first place since the cursor is already on an element eventually, depending where the user clicks to open the fancybox. But the jQuery mousemove event does fire on those.
I could just add a mousemove event in the MooTools file which triggers the mouseenter event, but for the sake of learning: how would I fire an elements event function (and make use of the this-reference)?
This didnt work for me.
MooTools:
$$('.foo').addEvents({
mouseenter: function(){
console.log('fired!'); // never does ):
// stuff happens here
}
});
jQuery:
$('#bar').fancybox({
onComplete: function() {
$('.foo').unbind('mousemove').mousemove(function() {
var el = this;
console.log('mousemoved');
$('.foo').unbind('mousemove');
// does not work:
(function($$) {
$$(this).fireEvent('mouseenter', $(this));
})(document.id);
// neither does this:
var event;
if (document.createEvent) {
event = document.createEvent("HTMLEvents");
event.initEvent("mousemove", true, true);
} else {
event = document.createEventObject();
event.eventType = "mousemove";
}
event.eventName = "mousemove";
event.memo = {};
if (document.createEvent) {
this.dispatchEvent(event);
}
else {
this.fireEvent("on" + event.eventType, event);
}
// whats the solution?
// something like: this.fireEvent('mouseenter', this); would be cool!
});
}
});
Just get to your jQuery Element and then call the fireEvent from the Element.prototype
// does not work:
(function($$) {
$$(this).fireEvent('mouseenter', $(this));
})(document.id);
to:
$(this)[0].fireEvent('mouseenter' /* optional event obj, { target: $(this)[0] } */);
// or as #Sergio suggests -
this.fireEvent('mouseenter'); // this == element anyway
So I have a regular onclick event attached to a few buttons, each function that handles the onclick event does something different (so I can't reuse the same function for both events).
element1.onclick = function() {
if(this.classList.contains('disabled') {
return false;
}
// For example make an AJAX call
};
element2.onclick = function() {
if(this.classList.contains('disabled') {
return false;
}
// For example hide a div
};
I'm writing duplicate code for this 'disabled' class check, I want to eliminate this by hooking in some common onclick check then fire the regular onclick event if that check passes.
I know the below won't work but I think it will illustrate what I'm trying to do:
document.addEventListener('click', function() {
// 1. Do the disabled check here
// 2. If the check passes delegate the event to the proper element it was invoked on
// 3. Otherwise kill the event here
});
I'm not using any JavaScript library and I don't plan to, in case someone comes up with 'Just use jQuery' type answers.
EDIT: Had to pass boolean third argument to addEventListener as true and everything is fine.
Use event capturing, like so:
document.addEventListener('click', function(event) {
if (/* your disabled check here */) {
// Kill the event
event.preventDefault();
event.stopPropagation();
}
// Doing nothing in this method lets the event proceed as normal
},
true // Enable event capturing!
);
Sounds like you need to set the capture flag to true and then use .stopPropagation() on the event if a certain condition is met at the target, f.ex:
document.addEventListener('click', function(e) {
if ( condition ) {
e.stopPropagation();
// do soemthing else, the default onclick will never happen
}
}, true);
Here is a demo: http://jsfiddle.net/v9TEj/
You can create a generic function that receives a callback:
//check everything here
function handleOnclick(callback) {
if(this.classList.contains("disabled")) {
return false;
} else {
callback(); //callback here
}
}
//and now on every onclick, just pass the custom behavior
element1.onclick = function() {
handleOnClick(function() {
console.log('element1 onclick fire'); // For example hide a div
});
};
element2.onclick = function() {
handleOnClick(function() {
console.log('element2 onclick fire'); // For example ajax request
});
};
Edit
Based on your latest comment, let me know if this rewrite works for you... only one biding this time.
element1.customFunction = function() {
handleOnClick(function() {
console.log('element1 onclick fire'); // For example hide a div
});
};
element2.customFunction = function() {
handleOnClick(function() {
console.log('element2 onclick fire'); // For example ajax request
});
};
document.addEventListener('click', function() {
//1. grab the element
//2. check if it has the customFunction defined
//3. if it does, call it, the check will be done inside
};
While working with browser events, I've started incorporating Safari's touchEvents for mobile devices. I find that addEventListeners are stacking up with conditionals. This project can't use JQuery.
A standard event listener:
/* option 1 */
window.addEventListener('mousemove', this.mouseMoveHandler, false);
window.addEventListener('touchmove', this.mouseMoveHandler, false);
/* option 2, only enables the required event */
var isTouchEnabled = window.Touch || false;
window.addEventListener(isTouchEnabled ? 'touchmove' : 'mousemove', this.mouseMoveHandler, false);
JQuery's bind allows multiple events, like so:
$(window).bind('mousemove touchmove', function(e) {
//do something;
});
Is there a way to combine the two event listeners as in the JQuery example? ex:
window.addEventListener('mousemove touchmove', this.mouseMoveHandler, false);
Any suggestions or tips are appreciated!
Some compact syntax that achieves the desired result, POJS:
"mousemove touchmove".split(" ").forEach(function(e){
window.addEventListener(e,mouseMoveHandler,false);
});
In POJS, you add one listener at a time. It is not common to add the same listener for two different events on the same element. You could write your own small function to do the job, e.g.:
/* Add one or more listeners to an element
** #param {DOMElement} element - DOM element to add listeners to
** #param {string} eventNames - space separated list of event names, e.g. 'click change'
** #param {Function} listener - function to attach for each event as a listener
*/
function addListenerMulti(element, eventNames, listener) {
var events = eventNames.split(' ');
for (var i=0, iLen=events.length; i<iLen; i++) {
element.addEventListener(events[i], listener, false);
}
}
addListenerMulti(window, 'mousemove touchmove', function(){…});
Hopefully it shows the concept.
Edit 2016-02-25
Dalgard's comment caused me to revisit this. I guess adding the same listener for multiple events on the one element is more common now to cover the various interface types in use, and Isaac's answer offers a good use of built–in methods to reduce the code (though less code is, of itself, not necessarily a bonus). Extended with ECMAScript 2015 arrow functions gives:
function addListenerMulti(el, s, fn) {
s.split(' ').forEach(e => el.addEventListener(e, fn, false));
}
A similar strategy could add the same listener to multiple elements, but the need to do that might be an indicator for event delegation.
Cleaning up Isaac's answer:
['mousemove', 'touchmove'].forEach(function(e) {
window.addEventListener(e, mouseMoveHandler);
});
EDIT
ES6 helper function:
function addMultipleEventListener(element, events, handler) {
events.forEach(e => element.addEventListener(e, handler))
}
ES2015:
let el = document.getElementById("el");
let handler =()=> console.log("changed");
['change', 'keyup', 'cut'].forEach(event => el.addEventListener(event, handler));
For me; this code works fine and is the shortest code to handle multiple events with same (inline) functions.
var eventList = ["change", "keyup", "paste", "input", "propertychange", "..."];
for(event of eventList) {
element.addEventListener(event, function() {
// your function body...
console.log("you inserted things by paste or typing etc.");
});
}
I have a simpler solution for you:
window.onload = window.onresize = (event) => {
//Your Code Here
}
I've tested this an it works great, on the plus side it's compact and uncomplicated like the other examples here.
One way how to do it:
const troll = document.getElementById('troll');
['mousedown', 'mouseup'].forEach(type => {
if (type === 'mousedown') {
troll.addEventListener(type, () => console.log('Mouse is down'));
}
else if (type === 'mouseup') {
troll.addEventListener(type, () => console.log('Mouse is up'));
}
});
img {
width: 100px;
cursor: pointer;
}
<div id="troll">
<img src="http://images.mmorpg.com/features/7909/images/Troll.png" alt="Troll">
</div>
AddEventListener take a simple string that represents event.type. So You need to write a custom function to iterate over multiple events.
This is being handled in jQuery by using .split(" ") and then iterating over the list to set the eventListeners for each types.
// Add elem as a property of the handle function
// This is to prevent a memory leak with non-native events in IE.
eventHandle.elem = elem;
// Handle multiple events separated by a space
// jQuery(...).bind("mouseover mouseout", fn);
types = types.split(" ");
var type, i = 0, namespaces;
while ( (type = types[ i++ ]) ) { <-- iterates thru 1 by 1
You can also use prototypes to bind your custom function to all elements
Node.prototype.addEventListeners = function(eventNames, eventFunction){
for (eventName of eventNames.split(' '))
this.addEventListener(eventName, eventFunction);
}
Then use it
document.body.addEventListeners("mousedown touchdown", myFunction)
// BAD: One for each event - Repeat code
textarea.addEventListener('keypress', (event) => callPreview);
textarea.addEventListener('change', (event) => callPreview);
// GOOD: One run for multiple events
"keypress change".split(" ").forEach((eventName) => textarea.addEventListener(eventName, callPreview));