Dynamic tracing of all event handler registrations in JavaScript - javascript

My goal is to dynamically trace all registrations of event handlers in JavaScript, including but not limited to the document and window object. A piece of code is supposed to run before the rest of the website executes (onLoad) and should report what functions register for events. I want to wrap addEventListener, onmouseclick, onmessage etc. but I don't want to do this for each object individually.
I was looking into modifying the behavior of Object.constructor to perform self-inspection in the constructor. The code should be pretty trivial and look roughly like this:
var toWrap = [ "addEventListener", "onclick", "..." ];
for ( var x in toWrap ) {
if ( toWrap[x] in this ) {
this[toWrap[x]] = wrap(toWrap[x], this[toWrap[x]]);
}
}
... but what is the right place to plug the code?
A pure JavaScript solution would be preferred over jQuery etc., I am implementing in PhantomJS.
Update:
I came up with the following code after reading this. It seems to work fine - any objections?
Object = ( function ( ) {
this.wrap = function(e) {
// ....
}
this.wrap();
});

Related

How to trigger an event with a custom event (specifically a custom dataTransfer property)?

I'm currently attempting to test some code that uses drag-and-drop. I found some other questions that were kinda related to this, but they were way too specific to help me, or not related enough.
This being a test, I'm struggling on trying to automatically execute code inside a .on('drop',function(e){....} event. The main issue is not that I can't run the code inside, but it's that I can't transfer the dataTransfer property, and I can't seem to fake it because it's read-only. Is there anyway to fake the dataTransfer property or otherwise get around it?
I came up with this JSFiddle that serves as a template of what I'm trying to do: https://jsfiddle.net/gnq50hsp/53/
Essentially if you are able to explain to me (if this is at all possible) how I can possibly fake the dataTransfer property, I should be all set.
Side notes:
I'm totally open to other ways of somehow getting inside that code, like for example, maybe its possible to trigger the event and pass in a fake event object with a fake dataTransfer object.
To see the drag-drop behavior, change the JavaScript load type from no-wrap head to on-Load, then you should see what I'm trying to simulate.
Important to note that I cannot modify any of the code inside the event handlers, only inside the outside function
Using Karma/Jasmine so use of those tools are also possible like spies
Also, I'm using Chrome.
Thanks in advance, and let me know for any questions/clarifications!
You should be able to override pretty much everything you want using Object.defineProperty. Depending on what you want to test it can be very simple or very complex. Faking the dataTransfer can be a bit tricky, since there's a lot of restrictions and behaviors linked to it, but if you simply want to test the drop function, it's fairly easy.
Here's a way, this should give you some ideas as to how to fake some events and data:
//Event stuff
var target = $('#target');
var test = $('#test');
test.on('dragstart', function(e) {
e.originalEvent.dataTransfer.setData("text/plain", "test");
});
target.on('dragover', function(e) {
//e.dataTransfer.setData('test');
e.preventDefault();
e.stopPropagation();
});
target.on('dragenter', function(e) {
e.preventDefault();
e.stopPropagation();
});
//What I want to simulate:
target.on('drop', function(e) {
console.log(e)
//Issue is that I can't properly override the dataTransfer property, since its read-only
document.getElementById('dataTransferDisplay').innerHTML = e.originalEvent.dataTransfer.getData("text");
});
function simulateDrop() {
// You'll need the original event
var fakeOriginalEvent = new DragEvent('drop');
// Using defineProperty you can override dataTransfer property.
// The original property works with a getter and a setter,
// so assigning it won't work. You need Object.defineProperty.
Object.defineProperty(fakeOriginalEvent.constructor.prototype, 'dataTransfer', {
value: {}
});
// Once dataTransfer is overridden, you can define getData.
fakeOriginalEvent.dataTransfer.getData = function() {
return 'test'
};
// TO have the same behavior, you need a jquery Event with an original event
var fakeJqueryEvent = $.Event('drop', {
originalEvent: fakeOriginalEvent
});
target.trigger(fakeJqueryEvent)
}
https://jsfiddle.net/0tbp4wmk/1/
As per jsfiddel link you want to achieve drag and drop feature. jQuery Draggable UI already provides this feature why you can not use that?
For create custom event on your way you have to follow two alternative ways
$('your selector').on( "myCustomEvent", {
foo: "bar"
}, function( event, arg1, arg2 ) {
console.log( event.data.foo ); // "bar"
console.log( arg1 ); // "bim"
console.log( arg2 ); // "baz"
});
$( document ).trigger( "myCustomEvent", [ "bim", "baz" ] );
On above example
In the world of custom events, there are two important jQuery methods: .on() and .trigger(). In the Events chapter, we saw how to use these methods for working with user events; for this chapter, it's important to remember two things:
.on() method takes an event type and an event handling function as arguments. Optionally, it can also receive event-related data as its second argument, pushing the event handling function to the third argument. Any data that is passed will be available to the event handling function in the data property of the event object. The event handling function always receives the event object as its first argument.
.trigger() method takes an event type as its argument. Optionally, it can also take an array of values. These values will be passed to the event handling function as arguments after the event object.
Here is an example of the usage of .on() and .trigger() that uses custom data in both cases:
OR
jQuery.event.special.multiclick = {
delegateType: "click",
bindType: "click",
handle: function( event ) {
var handleObj = event.handleObj;
var targetData = jQuery.data( event.target );
var ret = null;
// If a multiple of the click count, run the handler
targetData.clicks = ( targetData.clicks || 0 ) + 1;
if ( targetData.clicks % event.data.clicks === 0 ) {
event.type = handleObj.origType;
ret = handleObj.handler.apply( this, arguments );
event.type = handleObj.type;
return ret;
}
}
};
// Sample usage
$( "p" ).on( "multiclick", {
clicks: 3
}, function( event ) {
alert( "clicked 3 times" );
});
On above example
This multiclick special event maps itself into a standard click event, but uses a handle hook so that it can monitor the event and only deliver it when the user clicks on the element a multiple of the number of times specified during event binding.
The hook stores the current click count in the data object, so multiclick handlers on different elements don't interfere with each other. It changes the event type to the original multiclick type before calling the handler and restores it to the mapped "click" type before returning:

How to attach an event listener to a javascript var

I am a somewhat green programmer, and quite new to javascript/jquery, but I thought I understood javascript events. Apparently not. I am not able to get event listeners to work as I'd like.
Given javascript:
var Thing = {
//stuff
update: function() {
$.event.trigger({type:'stateUpdate', more:stuff});
}
};
var Room = {
//more stuff
updateHandler: function (e) {
//handle event here
}
};
If I do jquery:
$(document).on('stateUpdate', $Room.updateHandler);
then it works fine, but I can't do either
$(Room).on('stateUpdate', $Room.updateHandler);
or
Room.addEventListerner('stateUpdate', $Room.updateHandler);
The first does nothing, the second gives .addEventListerner is not a function error.
I've googled for hours and can't figure it out. I found something that said .addEventListener only works on objects that implement EventListener, something about handleEvent, and something about functions automatically implementing EventListener. Nothing on how to make an object implement it. Is there no way to add listeners to javascript objects that aren't functions? Am I going to have to create an event handler object, or use 'document' or 'window' and have it call handlers? That seems really ugly.
Should the objects be functions in the first place? Would that work? It seems the current opinion is that making everything functions is just trying to make javascript into something it isn't.
AFAIK there are no way to add a event listener to the plain object, as it is not placed inside DOM. Events are firing inside DOM, and bubbling so your event listener for custom object won't receive it.
There is a http://www.bobjs.com/ framework that can help you implement custom events.
In response to #Barmar (sort of) I believe I worked this out. Confirmation on if this is a a good alternative or not would be nice, though. Basically, I have to do a subscriber thing, right? Almost event/listener, but not quite.
var thing = {
callbacks: {},
regCallback: function (key, which) {
callbacks[key] = which;
},
remCallback: function (key) {
callbacks[key].delete;
}
update: function(e) {
for(var i = 0, len = callbacks.length; i < len;i++){
callbacks[i](e);
};
}
};
var Room = {
updateHandler: function () {
//handle stuff
},
subscribe: function (which, callback) {
which.regCallback('room', callback);
}
unsub: function (which) {
which.remCallback('room');
}
};
//wherever/whenever I need to get updates something like
Room.subscribe(thing, Room.updateHandler);
//unsub
Room.unsub(thing);
Second error is caused by typo: addEventListerner has extra r in it.

Keeping bookmarklet from generating multiple event listeners

I currently am working on a bookmarklet that opens an iframe, and sets up a communication of postMessage back and forth. That all works fine.
However, seemingly because the bookmarklet is being loaded as an anonymous function, the listeners are multiplying if I run the bookmarklet more than once on a page.
Is there some sort of way to keep track of these addEventListeners so that they don't double-up?
Do I need to define the rp_receive_message outside of the anonymous function?
Here's an example of the code:
var rp_receive_message = function (e) {
var response = e.data;
console.log("got message with "+ response);
};
if (window.addEventListener) {
window.addEventListener('message', rp_receive_message, false);
} else {
window.attachEvent('onmessage', rp_receive_message);
}
var s1 = window.document.createElement('iframe');
s1.setAttribute('src', 'http://mydomain.com/iframe.html');
s1.setAttribute('id', 'testiframe');
s1.setAttribute('width', '700');
s1.setAttribute('height', '550');
s1.setAttribute('frameBorder', '0');
s1.setAttribute('onload', 'this.contentWindow.postMessage(window.location.href, "http://mydomain.com/iframe.html");');
document.getElementById('container').appendChild(s1);
Probably this will solve the problem:
window.onmessage = rp_receive_message;
As you suggest, the code below might be enough by itself. I don't know if addEventListener and attachEvent will add the same function multiple times, but I wouldn't at all be surprised if they will. I suggest just testing it.
window.rp_receive_message = function(){...}
If you dislike either solution, you've got to set up a global variable, which hardly seems any different or greatly superior to above. The global can be a simple boolean to check if the event has been attached, or it can be a list of attached events that you update yourself. AFAIK, and I'm pretty sure, there is no native JS solution to get a list of event listeners have been attached to a particular event. Libraries such as jQuery maintain lists and let you read them; and possibly have other techniques that are elegant solutions to your general problem.

Listening and firing events with Javascript and maybe jQuery

In my JavaScript and Flex applications, users often perform actions that I want other JavaScript code on the page to listen for. For example, if someone adds a friend. I want my JavaScript app to then call something like triggerEvent("addedFriend", name);. Then any other code that was listening for the "addedFriend" event will get called along with the name.
Is there a built-in JavaScript mechanism for handling events? I'm ok with using jQuery for this too and I know jQuery makes extensive use of events. But with jQuery, it seems that its event mechanism is all based around elements. As I understand, you have to tie a custom event to an element. I guess I can do that to a dummy element, but my need has nothing to do with DOM elements on a webpage.
Should I just implement this event mechanism myself?
You have a few options:
jQuery does allow you to do this with objects not associated with the document. An example is provided below.
If you're not already using jQuery on your page, then adding it is probably overkill. There are other libraries designed for this. The pattern you are referring to is called PubSub or Publish/Subscribe.
Implement it yourself, as you've suggested, since this is not difficult if you're looking only for basic functionality.
jQuery example:
var a = {};
jQuery(a).bind("change", function () {
alert("I changed!");
});
jQuery(a).trigger("change");
I would implement such using MVVM pattern with knockjs library.
Just create an element, and use jquery events on it.
It can be just a global variable, doesn't even have to be connected to the DOM.
That way you accomplish your task easily and without any extra libs.
Isn't it possible to bind onchange events in addition to click events? For instance, if addFriend is called and modifies a list on the page, you could bind the change event to then invoke additional functionality.
$('#addFriendButton').click( function() {
// modify the #friendList list
});
$('#friendList').change( function() {
myOtherAction();
});
This is total Host independent, no need for jQuery or dom in this case!
function CustomEvents(){
//object holding eventhandlers
this.handlers_ = {};
}
//check if the event type does not exist, create it.
//then push new callback in array.
CustomEvents.prototype.addEventListner = function (type, callBack){
if (!this.handlers_[type]) this.handlers_[type] = [];
this.handlers_[type].push(callBack);
}
CustomEvents.prototype.triggerEvent = function (type){
//trigger all handlers attached to events
if (!this.handlers_[type]) return;
for (var i=0, handler; handler = this.handlers_[type][i]; i++)
{
//call handler function and supply all the original arguments of this function
//minus the first argument which is the type of the event itself
if (typeof handler === "function") handler.apply(this,arguments.slice(1));
}
}
//delete all handlers to an event
CustomEvents.prototype.purgeEventType = function(type){
return delete this.handlers_[type];
}
test:
var customEvents = new CustomEvents();
customEvents.addEventListner("event A", function(arg){alert('Event A with arguments' + arg);));
customEvents.triggerEvent("event A", "the args");
EDIT added arguments passing

In javascript, how can I wrapper an onclick callback?

I want to decorate or wrapper a specific onclick callback such that my code is executed after the original installed callback is called.
I started with this in order to do it in a semi-python'ic way using function generator with closure:
var original_onclick = node.onclick;
function wrapper_onclick() {
original_onclick.apply(this,arguments);
my_code();
}
node.onclick = wrapper_onclick;
However, when I use the above code, the original callbacks aren't working properly. Am I passing in the wrong first parameter (context parameter?) this. Do I need to tweak the arguments? Is this already in list arguments?
This is exactly why lots of people worked to find a proper solution for cross browser event listening. Some browsers don't fire the function in the right context, some browsers don't pass the event argument and so on. It's a lot easier to use a framework, but if you insist on using no frameworks...
First if you have really horrible code like:
<a href="javascript:someFunction( this , evaledVariable );otherFn( ohDearVariable )">
Then sorry, no one can help you.
Otherwise...
In any case, you can't attach events directly to a node by simply setting the .onclick attribute and expect it to behave equally cross browser or even work properly in the browser you're developing with. You must use both addEventListener and attachEvent methods.
The most simplest way of forking for cross browser events is:
var addListener = function( node , event , listener ) {
if ( node.addEventListener ) {
node.addEventListener( event , listener , false );
} else if (node.attachEvent ) {
node.attachEvent( "on" + event , listener );
}
}
Same for removing listeners:
var removeListener = function( node , event , listener ) {
if ( node.addEventListener ) {
node.removeEventListener( event , listener , false );
} else if (node.attachEvent ) {
node.detachEvent( "on" + event , listener );
}
}
And use it thus:
var nodeListener = function( e ) {
alert(e.target);
// run once for demo
removeListener( node , "click" , nodeListener );
}
// notice we omit the "on" part.
addListener( node , "click" , nodeListener );
So if you want to wrap listeners appended to node the WRONG onclick way:
var wrongOnClick = node.onclick;
var listener = function( e ) {
before();
wrongOnClick.call( this , e );
after();
};
addListener( node , "click" , listener );
This will serve most modern browsers including IE, though actually a lot more optimalisation and memory leak prevention code is needed for a good implementation, but instead of writing this code yourself or having others do it for you, use a framework/tookit! Because by the time you have a good implementation running that works cross browser, you will have more code bloat then when you actually used a framework...
** Addendum **
As a hack, I still suggest to use the addEventListener method and keep references to your listeners.
var aListener = function( e ) { }
node.addEventListener( "click" , aListener , false );
var wrappedListener = function ( e ) {
before( e );
aListener( e );
after( e );
}
node.removeEventListener( "click" , aListener , false );
node.addEventListener( "click" , wrappedListener , false );
This obviously only works with your listeners, if you want it to wrap existing listeners in a page (listeners from others), then there's simply no way of knowing how people appended their listeners to nodes. If they used addEventListener/attachEvent libraries or the dirty example in the beginning of this post, you simply cannot reliably wrap them without manual coding and looking up the references or manually exposing them.
Based on help from Matt and BGerrissen, the following code works great for chrome:
var original_onclick = node.onclick;
function wrapper_onclick(e ) {
var ret = original_onclick(e);
remove_story_onclicks();
return ret;
}
node.onclick = wrapper_onclick;
Still not exactly sure how to do a bulletproof wrapper like the following in python:
def wrappee(foo,bar,baz):
print foo,bar,baz
def wrapper(*args,**kwargs):
print 'before'
wrappee(*args,**kwargs)
print 'after'
wrapper('a','b','c')
which prints
before
a b c
after

Categories

Resources