How to get the names of functions from event for stopPropagation - javascript

I'm trying to figure out if there is a way to get all of the functions called from an onclick event.
The scenario is something like this:
HTML:
<div onclick="a(); b();">
<a href="javascript();" onclick="c(event);>link</a>
</div>
JS:
var a = function() { console.log('a called'); };
var b = function() { console.log('b called'); };
var c = function(e) {
if (e.call['a']) { e.call['a'].stopPropagation(); }
console.log('b and c called, but not a.');
};
I want to use this to control which functions are stopped from propagating.

I'm afraid there are lots of incantations in this code. You should try and read more about the event flow, and to a lesser extent (because the subject is harder) about javascript execution contexts and possibly the javascript pseudo-protocol.
If I understand you correctly, you want a certain listener to be skipped if a previous listener has already been executed in the event flow - or, more probably, you want some content to be skipped, if some other content is executed first.
The user agent gives you no way to monitor the state of the execution of your listeners, so you need to manage that state yourself. Each listener must register that it has been called, so that next listeners can consult this state and adjust their logic. Or, as such an approach is likely to generate too much complexity (because of the tight coupling between listeners), you'd probably be better off writing some light rules framework, based on your needs.
You've asked a very specific, technical question, but do not hesitate to provide more context (the "why"), as the proper solution should be more a design-based one, rather than a technical-based one.

You could set global variables and set them when they are being called, for example:
aClicked = false;
bClicked = false;
function a() { aClicked = true; }
function b() { bClicked = true; }
var c = function() {
if (!aClicked && bClicked) {
console.log('b and c called, but not a.');
}
};

Related

How would you implement an 1. asynchronous event queue that 2. has event coalescing capabilities

Setting:
let's say three events happen in three separate part of some application, the event should be handled by two controllers. The application dispatcher is responsible for sending/receiving events from all parts of the application, and this dispatcher should have an asynchronous event queue.
In some cases, two the of three events are related along some attribute but are not of the same name, and only one should be passed to the controller, the other one may be discarded.
Problem:
Currently, I have a 'queue' that really just bounces the event to the controller, this is unhelpful because I have no way of comparing two events in the queue if only one is ever there at time.
So how do I ensure the events should stay in the queue for a while? I suppose a timeout function could do the trick but is there a better way?
To give credit where it's due, the idea of coalescing events is inspired by Cocoa and I'm basically trying to do something similar.
I don't know much about Cocoa, but I assume that it replaces older events with the latest event until the event is able to be dispatched to the application (i.e. if the application is busy for some reason). I'm not sure what your particular use case is, but if you want to rate-limit the events, I would use setTimeout like this:
function Dispatcher(controllers) {
this.controllers = controllers;
this.events = [];
this.nextController = 0;
}
Dispatcher.prototype = {
_dispatch: function (i) {
var ev = this.events.splice(i, 1);
this.controllers[this.nextController].handle(ev);
this.nextController = (this.nextController + 1) % this.controllers.length;
},
notify: function (ev) {
var index = -1, self = this, replace;
function similer(e, i) {
if (e.type === ev.type) {
index = i;
return true;
}
}
replace = this.events.some(similar);
if (replace) {
this.events[i] = ev;
} else {
// it's unique
index = this.events.push(ev) - 1;
setTimeout(function () {
self._dispatch(index);
}, 100);
}
}
};
Just call notify with the event (make sure there's a type property or similar) and it will handle the magic. Different types of events will be handled uniquely with their own setTimeout.
I haven't tested this code, so there are probably bugs.
I suppose a timeout function could do the trick but is there a better way?
No, there really isn't.
Usually the way to go is using setTimeout(..., 0) if your events are dispatched in the same run loop. So the implementation would look something like this:
var eventQueue = [];
var handlerTimer = -1;
function fireEvent(event, params) {
eventQueue.push([event, params]);
window.clearTimeout(handlerTimer);
handlerTimer = window.setTimeout(resolveQueue, 0);
}
function resolveQueue() {
// process eventQueue and remove unwanted events...
// then dispatch remaining events
eventQueue = [];
}
If you need to handle events from different run loops (for example native events like mouseup and click), you need to use some timeout value other than 0. The exact value depends on how long you want to accumulate events.

setTimeout in methods of multiple objects of the same type

I need help with the use of "setTimeout" in the methods of the objects of the same type. I use this code to initiate my objects:
function myObject(param){
this.content = document.createElement('div');
this.content.style.opacity = 0;
this.content.innerHTML = param;
document.body.appendChild(this.content);
this.show = function(){
if(this.content.style.opacity < 1){
this.content.style.opacity = (parseFloat(this.content.style.opacity) + 0.1).toFixed(1);
that = this;
setTimeout(function(){that.show();},100);
}
}
this.hide = function(){
if(this.content.style.opacity > 0){
this.content.style.opacity = (parseFloat(this.content.style.opacity) - 0.1).toFixed(1);
that = this;
setTimeout(function(){that.hide();},100);
}
}
}
Somewhere I have 2 objects:
obj1 = new myObject('Something here');
obj2 = new myObject('Something else here');
Somewhere in the HTML code I use them:
<button onclick="obj1.show()">Something here</button>
<button onclick="obj2.show()">Something else here</button>
When the user presses one button, everything goes OK, but if the user presses one button and after a short time interval he presses the other one, the action triggered by the first button stops and only the action of the second button is executed.
I understand that the global variable "that" becomes the refence of the second object, but I don't know how to create an automatic mechanism that wouldn't block the previously called methods.
Thank you in advance and sorry for my English if I made some mistakes :P
If you need something cancellable, use window.setInterval instead of setTimeout. setInterval returns a handle to the interval which can then be used to cancel the interval later:
var global_intervalHandler = window.setInterval(function() { ... }, millisecondsTotal);
// more code ...
// later, to cancel this guy:
window.clearInterval(global_intervalHandler);
So from here I'm sure you can use your engineering skills and creativity to make your own self expiring operations - if they execute and complete successfully (or even unsuccessfully) they cancel their own interval. If another process intervenes, it can cancel the interval first and hten fire its behavior.
There are several ways to handle something like this, here's just one off the top of my head.
First of all, I see you're writing anonymous functions to put inside the setTimeout. I find it more elegant to bind a method of my object to its scope and send that to setTimeout. There's lots of ways to do hitching, but soon bind() will become standard (you can write this into your own support libraries yourself for browser compatibility). Doing things this way would keep your variables in their own scope (no "that" variable in the global scope) and go a long way to avoiding bugs like this. For example:
function myObject(param){
// ... snip
this.show = function(){
if(this.content.style.opacity < 1){
this.content.style.opacity = (parseFloat(this.content.style.opacity) + 0.1).toFixed(1);
setTimeout(this.show.bind(this),100);
}
}
this.hide = function(){
if(this.content.style.opacity > 0){
this.content.style.opacity = (parseFloat(this.content.style.opacity) - 0.1).toFixed(1);
setTimeout(this.hide.bind(this),100);
}
}
}
Second, you probably want to add some animation-handling methods to your object. setTimeout returns handles you can use to cancel the scheduled callback. If you implement something like this.registerTimeout() and this.cancelTimeout() that can help you make sure only one thing is going on at a time and insulate your code's behavior from frenetic user clicking like what you describe.
Do you need that as global variable ? just change to var that = this; you will use variable inside of the function context.

Safely using hook events in JavaScript

In source code here
http://www.daftlogic.com/sandbox-javascript-slider-control.htm
There is these instructions:
// safely hook document/window events
if (document.onmousemove != f_sliderMouseMove) {
window.f_savedMouseMove = document.onmousemove;
document.onmousemove = f_sliderMouseMove;
}
I don't understand what it does and why it would be safer to do that this way, does someone understand?
It might be that some other code already assigned an event handler to document.onmousemove. The problem with this method, as opposed to addEventListener, is that only one function can be assigned to element.onXXXX. Thus, if you blindly assign a new event handler, an already existing one might be overwritten and other code might break.
In such a case, I would write:
if (document.onmousemove) {
(function() {
var old_handler = document.onmousemove;
document.onmousemove = function() {
old_handler.apply(this, arguments);
f_sliderMouseMove.apply(this, arguments);
};
}());
}
else {
document.onmousemove = f_sliderMouseMove;
}
This way it is ensured that both event handlers are executed. But I guess that depends on the context of the code. Maybe f_sliderMouseMove calls window.f_savedMouseMove anyway.
It is just saving the current hook, presumably so it can call it at the end of its own hook method.
It avoids stamping on some other codes hook that was already set up.
You would expect the hook code to be something like:
f_sliderMouseMove = function(e) {
// Do my thing
// Do their thing
window.f_savedMouseMove();
}
[obligatory jquery plug] use jquery events and you can ignore problems like this...
It appears that this code is storing the function that is currently executed on a mouse move, before setting the new one. That way, it can presumably be restored later, or delegated to, if need be. This should increase compatibility with other code or frameworks.

setting events programmatically

I am setting the className of a table row in my code, is it possible to do something similiar to set an event on a row? This is along the lines of what I would like to do :
for (var i = 1; i < numRows; i++) {
var ID = table.rows[i].id;
if (i % 2 == 0) {
table.rows[i].className = "GridRow";
table.rows[i].onmouseout = "GridRow";
}
else {
table.rows[i].className = "GridRowAlt";
table.rows[i].onmouseout = "GridRowAlt";
}
}
Yes, you can assign a function instance to the event handler that way:
table.rows[i].onmouseout = function() { ... };
Be careful doing that in loops, because you're creating a new function on every loop and the function closes over the data in scope (and so has an enduring reference to it, not a copy of it as of when the function was created; see this other recent question for more). But don't worry, closures are not complicated once you understand how they work.
In general, this is called "DOM0" event handling because it involves a method of attaching event handlers that was created prior to the first DOM specification. As of DOM2, there's a better way addEventListener:
table.rows[i].addEventListener('mouseout',function() { ... }, false);
It's "better" because you can have more than one event handler on the same event of the same element, whereas with the DOM0 mechanism, assigning a new event handler disconnects the previous one (if any).
On IE prior to IE9, sadly, addEventListener wasn't supported but it did have the very similar attachEvent:
table.rows[i].attachEvent('onmouseout',function() { ... });
Note the differences:
addEventListener's event names don't have the "on" prefix
addEventListener has one more param than attachEvent, which you almost always want to set false
Update:
All of the examples above are for inline anonymous functions, which is a bit unlike me, because I don't like anonymous functions. So just for clarity, from an events perspective, a function is a function. It can be a named function you declare elsewhere, or an inline anonymous function, whatever:
// Hook up...
table.rows[i].addEventListener('mouseout', handleRowMouseOut, false);
// Nice, reusable function defined elsewhere
function handleRowMouseOut(event) {
// ...
}
Off-topic: It's these sorts of browser differences that lead me to geneerally recommend using a library like jQuery, Prototype, YUI, Closure, or any of several others. They smooth over differences for you as well as providing lots of handy utility functions.
table.rows[i].onmouseout = "GridRow"; doesn't make a lot of sense, table.rows[i].onmouseout = function(){alert('hello');}; or some other valid script ought to work though.
Why don't you just use jQuery or some other JavaScript framework? This way your code gets more simple.
var i = 0;
$('#some_table tr').each(function() {
if (i % 2 == 0) {
$(this).addClass('GridRow');
$(this).mouseout(function(evt) { /* your GridRow function */ });
} else {
$(this).addClass('GridRowAlt');
$(this).mouseout(function(evt) { /* your GridRowAlt function */ });
}
i++;
})
Sultan
The original question is not to alert "GridRow". I'm quit sure GridRow is a function name. Fortunately each function is a child of window so write window["GridRow"].
I would add a well known bind-event function, because you need it quite often.
var bindEvent=function(elem,evt,func){
if(elem.addEventListener){
elem.addEventListener(evt,func,false);
}
else if(elem.attachEvent){
elem.attachEvent('on'+evt,function(){
func.call(event.srcElement,event);
})
}
};
and then:
bindEvent(table.rows[i],"mouseout",window["GridRow"]);

jQuery temporary unbinding events

Maybe I'm totally missing something about even handling in jQuery, but here's my problem.
Let's assume there are some event binding, like
$(element).bind("mousemove", somefunc);
Now, I'd like to introduce a new mousemove binding that doesn't override the previous one, but temporarily exclude (unbind) it. In other words, when I bind my function, I must be sure that no other functions will ever execute for that event, until I restore them.
I'm looking for something like:
$(element).bind("mousemove", somefunc);
// Somefunc is used regularly
var savedBinding = $(element).getCurrentBinding("mousemove");
$(element).unbind("mousemove").bind("mousemove", myfunc);
// Use myfunc instead
$(element).unbind("mousemove", myfunc).bind("mousemove", savedBindings);
Of course, the somefunc is not under my control, or this would be useless :)
Is my understanding that is possible to bind multiple functions to the same event, and that the execution of those functions can't be pre-determined.
I'm aware of stopping event propagation and immediate event propagation, but I'm thinking that they are useless in my case, as the execution order can't be determined (but maybe I'm getting these wrong).
How can I do that?
EDIT: I need to highlight this: I need that the previously installed handler (somefunc) isn't executed. I am NOT defining that handler, it may be or may be not present, but its installed by a third-party user.
EDIT2: Ok, this is not feasible right now, I think I'm needing the eventListenerList, which is not implemented in most browsers yet. http://www.w3.org/TR/2002/WD-DOM-Level-3-Events-20020208/changes.html
Another way could be to use custom events, something along these lines:
var flag = 0;
$(element).bind("mousemove", function() {
if(flag) {
$(this).trigger("supermousemove");
} else {
$(this).trigger("magicmousemove");
}
}).bind("supermousemove", function() {
// do something super
}).bind("magicmousemove", function() {
// do something magical
});
$("#foo").click(function() {
flag = flag == 1 ? 0 : 1; // simple switch
});
Highly annoying demo here: http://jsfiddle.net/SkFvW/
Good if the event is bound to multiple elements:
$('.foo').click(function() {
if ( ! $(this).hasClass('flag')) {
do something
}
});
(add class 'flag' to sort of unbind, add it to 'bind')

Categories

Resources