I'm looking for a way to achieve the following. I could build some mechanism to do this, I'm asking for something built-in or a really simple way, if it exists and I'm missing it.
Edit:
Please note that I'm talking about events in random objects, not in DOM elements. The events order cannot be managed using the parent, etc. as suggested in the possible duplicate.
Edit 2:
Maybe an after-all-handlers-have-been-called callback? Or an always-last-to-be-executed handler?
Given:
var someObject={};
$(someObject).on("event",function() { console.log('Default handler'); });
...
$(someObject).on("event",function() { console.log('Other handler'); });
When doing:
$(someObject).triggerHandler("event");
The output is:
Default handler
Other handler
We all know this. The problem is: What if I would want to make the first event the "default" handler, to be executed if there aren't other event handlers (not a problem there) or if the other handlers didn't stop the event propagation (here is the problem).
I'm looking for a way to be able to do something like:
$(someObject).on("event",function(ev) { ev.preventDefault(); });
and prevent the first event handler from executing. In this example is not working given the execution order. Reversing the execution order is not the correct way to do it.
TL;DR
Is it possible to set a default handler, one to be called in case there's no other handlers and the event hasn't been canceled?
Edit 3: To give you a better idea, the current approach is the following (names are made up for this example):
var eventCanceled=false;
function doTheEvent() {
$(someObject).triggerHandler("event");
if(!eventCanceled) defaultEventHandler();
}
//To be called inside the handlers to stop the default handler
function cancelTheEvent() {
eventCanceled=true;
}
I just want to get rid of this and be able to use triggerHandler directly.
Hope this is what you are looking for. This is called observer pattern.
var someObj = {};
someObj.eventCallbackList = {};
someObj.createEventObject = function(name) {
return {
type: name,
preventDefault: function() {
this.callDefault = false;
},
callDefault: true
}
}
someObj.on = function(eventName, callback, defaultFlag) {
if (!this.eventCallbackList[eventName]) {
// there can be multiple other handlers
this.eventCallbackList[eventName] = {
other: [],
default: null
};
}
if (defaultFlag) {
this.eventCallbackList[eventName]['default'] = callback;
} else {
this.eventCallbackList[eventName]['other'].push(callback);
}
}
someObj.triggerHandler = function(eventName) {
var event = this.createEventObject(eventName);
var callbacks = this.eventCallbackList[eventName];
if (callbacks) {
if (callbacks['other']) {
for (var i = 0; i < callbacks['other'].length; i++) {
callbacks['other'][i](event);
}
}
if (event.callDefault && callbacks['default']) {
callbacks['default'](event);
}
}
}
// Test
someObj.on('myCustomEvent', function(event) {
console.log('OtherHandler');
event.preventDefault();
});
someObj.on('myCustomEvent', function(event) {
console.log('default');
}, true);
$(document).on('click', function() {
someObj.triggerHandler('myCustomEvent');
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
you can add a parameter to set what codes you want to execute inside your triggerHandler function.
you can refer to this thread in adding parameter.
jQuery: receive extraParameters from triggerHandler()
While there is a way to detect if the preventDefault() was called, there is no such thing as a "default handler". Event handlers are executed in the order they are registered. So the first one registered is the first one executed.
If your "default handler" doesn't need to be executed synchronously, you could delay the "default handler" to be executed after all the other handlers are done and in any of the other handlers revert that if necessary:
var defaultHandlerTimeout;
$element.on('event', function() {
defaultHandlerTimeout = setTimeout(function() {
// your actual handler
});
});
$element.on('event', function() {
// prevent the "default handler" from being executed
clearTimeout(defaultHandlerTimeout);
});
jQuery does store all of it's events internally and you can access specific element events using $._data(elem,'events').
Whether this will help your situation or not I'm not sure but is worth digging into. Personally i've never found the need to use it.
See https://stackoverflow.com/a/2518441/1175966
Related
I am wondering if there is an option in Socket.IO client library for Node.js for replacing listener function for specific event.
I have this simple function:
var Service = module.exports = function(address) {
var _socket = require('socket.io-client')(address);
this.connectAccount = function(account, callback) {
_socket.on('account/connected', function (response) {
callback(response.data, response.message);
});
_socket.emit('account/connect', account.getParameters());
};
}
The problem is, when I call function connectAccount() several times, all anonymous functions I pass each time in on() function get also called and after short while it reaches the limit and throws error.
So my question is if there is a way how to replace each time that listener so each time it gets called only once?
Thanks in advance.
You could either remove the listener before attaching new one or check if a listener is attached.
To remove listener:
_socker.removeListener('account/connected', [function]);
To check if an event has listeners:
_socker.hasListeners('account/connected');
An eventEmitter supports multiple event handlers. It does not have a mechanism for "replacing" an event handler. As such, you have a couple options:
You can keep a flag for whether the event handler is already set.
You can remove the listener before you add it. Removing it if it wasn't already set is just a noop.
Code example for only installing the event handler once:
var Service = module.exports = function(address) {
var initialized = false;
var _socket = require('socket.io-client')(address);
this.connectAccount = function(account, callback) {
if (!initialized) {
_socket.on('account/connected', function (response) {
callback(response.data, response.message);
});
initialized = true;
}
_socket.emit('account/connect', account.getParameters());
};
}
I asked a question yesterday, I accepted the answer, but sometime later, I came to know that the solution was not complete. The question is :-
Insert a JQuery click handler that executes before the ones already registered
using setTimeout(handler,0); returns immediately and I can not use return setTimeout(handler,0);. How can I run this handler synchronously and not allow the parent function to complete until this handler is completely executed and I get the return value out of it ?
I am in hurry, so I am asking it again, rather than editing that question again.
You don't need to use setTimeout. If u don't use setTimeout, your handler reamins synchronous, and you can return all the value u want in your function.
<script>
function f2() {
alert('Handler declared in HTML')
}
buttons = document.getElementsByTagName('input'); // refine this, later
for (i = 0, max = buttons.length; i < max; i++) {
oldonclick = buttons[i].onclick;
buttons[i].onclick = function() {
alert('Prepend handler');
oldonclick();
}
}
</script>
Since timeouts are asynchronous you’ll need to set the variable from within the timeout (and/or call a callback function).
var x = 1;
setTimeout(function() {
x = 2;
}, 2000);
Here’s an example with a callback function. You need this is you want to do something with the variable as soon as it’s changed.
var x = 1;
function callback(x) {
console.log(x);
}
setTimeout(function() {
x = 2;
callback(x);
}, 2000);
This will log 2 as soon as the timeout is executed.
Depending on what exactly it is you’re trying to do, you may not need timeouts at all, which avoids asynchronicity and a lot of trouble.
Quick answar: What about changing:
setTimeout(clickhandler, 0);
to
eval(clickhandler)();
to
eval(clickhandler);
$(document).ready(function() {
$("input[type=button]").each(function() {
// your button
var btn = $(this);
// original click handler
var clickhandler = btn.data("events").click[0];
btn.unbind("click", clickhandler);
// new click handler
btn.click(function() {
alert('Prepended Handler');
clickhandler();
});
});
});
function f2() {
alert('Handler declared in HTML');
}
And now clickhandler is a function, right?
See: jQuery: Unbind event handlers to bind them again later
Apologies if I appear a little "noobish" with events, but for whatever reason the following doesn't work for me:
var someDomRef = document.getElementByRef("refVal");
for(i=0;i<someDomRef.length;i++) { //or someDomRef.childNodes.length/someDomRef.TagRef.length
someDomRef.onmouseup = function() {
someDomRef.childNodes[i].onmouseover=function() {
if(someRef.onmouseup) {
//return false for the onmouseover handler of this(someDomRef.childNodes[i])
}
};
};
}
Each time I release the mouse button after holding it upon someDomRef, I find a "onmouseover could not be assigned to undefined object" error in the JS console. Any help would be greatly appreciated for solving this problem (note: I know that I can assign another event handler outside of the onmouseover function to itself on the condition of someDomRef.onmouseup, but I'd like to know of a way to achieve this from within that onmouseover itself (I've also tried assigning var x = someDomRef.childNodes[i] and passing it through as an argument to the conditional clause for someRef.onmouseup, but this doesn't work either (albeit it doesn't return an error for this attempt)).
i is undefined when mouseup is called. You need to close over this with some closure functions:
var someDomRef = document.getElementByRef("refVal");
for(i=0;i<someval;i++) {
(function(i) {
someDomRef.onmouseup = function() {
someDomRef.childNodes[i].onmouseover=function() {
if(someRef.onmouseup) {
//return false for the onmouseover handler of this(someDomRef.childNodes[i])
}
};
};
})(i);
}
You may need another closure inside of the onmouseup function
var Helloworld = {
onLoad: function() {
// initialization code
this.initialized = true;
},
onMenuItemCommand: function() {
window.open("chrome://helloworld/content/hello.xul", "", "chrome");
}
};
window.addEventListener("load", function(e) { Helloworld.onLoad(e); }, false);
http://kb.mozillazine.org/Getting_started_with_extension_development
I don't understand the function(e) { Helloworld.onLoad(e); part. I think it passes an event parameter e to the onLoad function, but the onLoad function doesn't have onLoad: function(e) {} to receive e, so what's going on?
Just defines an anonymous function: the said function will will be called when the event load is triggered.
Note that in JavaScript, the function declaration isn't strict. One can call a function with parameters even if the declaration doesn't explicitly show such. In other words, there is no such thing as a "function signature" (like in Java, C++ etc.). The JavaScript interpreter will only call the "hasmethod" method on the object to determine if "method X" is implemented.
var Helloworld = {
// parameters can be sent to "onload" but they will be ignored.
onLoad: function() {
// initialization code
this.initialized = true;
},
onMenuItemCommand: function() {
window.open("chrome://helloworld/content/hello.xul", "", "chrome");
}
};
// attach an event handler to the event "load". Pass the event variable "e"
// even though the receiving function will ignore it.
window.addEventListener("load", function(e) { Helloworld.onLoad(e); }, false);
You can change the onLoad if you want to have a parameter, it's just an example onLoad. It's JavaScript, after all, not C/C++ etc.
The following confirmDialog function is called midway through another jquery function. When this confirmDialog returns true the other function is supposed to continue... but it doesn't. The reason for this seems to be that the entire confirmDialog function has already executed (returning false) by the time the continue button gets clicked. How can I delay it returning anything until after the buttons have been clicked?
(Or, if I'm completely on the wrong track, what is the problem and how do I fix it?)
function confirmDialog(message) {
....
$('input#continue', conf_dialog).click(function() {
$(this).unbind();
$('p',conf_dialog).fadeOut().text('Are you really sure you want to '+message).fadeIn();
$(this).click(function() {
$(conf_dialog).remove();
return true;
});
});
$('input#cancel', conf_dialog).click(function() {
$(conf_dialog).remove();
return false;
});
}
Im' not sure you can.
AFAIK only built-in function like confirm, alert or prompt can be blocking while asking for an answer.
The general workaround is to refactor your code to use callbacks (or use the built-in functions). So that would mean splitting your caller function in two, and executing the second part when the input is obtained.
In confirmDialog, you're setting up event handlers, that will execute when events are fired, not when confirmDialog is run. Another issue, is that you return true or false inside the event function, so that won't apply to the outer function confirmDialong.
The part that relies on the button presses would need to be re-factored. Perhaps put it in another function, and call it from the click handlers:
var afterConfirm = function(bool) {
if(bool) {
//continue clicked
} else {
//cancel clicked
}
//do for both cases here
}
//inside confirmDialog input#continue
$(this).click(function() {
$(conf_dialog).remove();
afterConfirm(true);
});
You may want to look into using Deferred objects. Here are two links that explain them.
http://www.sitepen.com/blog/2009/03/31/queued-demystifying-deferreds/
http://api.dojotoolkit.org/jsdoc/1.3/dojo.Deferred
Using a Deferred you could take your calling function:
function doSomething () {
// this functions does something before calling confirmDialog
if (confirmDialog) {
// handle ok
} else {
// handle cancel
}
// just to be difficult lets have more code here
}
and refactor it to something like this:
function doSomethingRefactored() {
// this functions does something before calling confirmDialog
var handleCancel = function() { /* handle cancel */};
var handleOk = function() { /* handle ok */};
var doAfter = function() { /* just to be difficult lets have more code here */};
var d = new dojo.deferred();
d.addBoth(handleOk, handleCancel);
d.addCallback(doAfter);
confirmDialog(message, d);
return d;
}
ConfirmDialog would have to be
updated to call d.callback() or
d.errback() instead of returning true
or false
if the function that calls
doSomething needs to wait for
doSomething to finish it can add its
own functions to the callback chain
Hope this helps... it will make a lot more sense after reading the sitepen article.
function callingFunction() {
$('a').click(function() {
confirmDialog('are you sure?', dialogConfirmed);
// the rest of the function is in dialogConfirmed so doesnt
// get run unless the confirm button is pressed
})
}
function dialogConfirmed() {
// put the rest of your function here
}
function confirmDialog(message, callback) {
...
$('input#continue', conf_dialog).click(function() {
callback()
$(conf_dialog).remove();
return false;
}),
$('input#cancel', conf_dialog).click(function() {
$(conf_dialog).remove();
return false;
})
...
}
You could add a timeout before the next function is called
http://www.w3schools.com/htmldom/met_win_settimeout.asp