I have a setup theoretically like this [see fiddle -> http://jsfiddle.net/GeZyw/] :
var EventTest = function(element) {
this.element = element;
this.element.addEventListener('click', elementClick);
function elementClick() {
var event = document.createEvent('CustomEvent');
event.initEvent('myevent', false, false);
event['xyz']='abc';
event.customData='test';
console.log(event);
this.dispatchEvent(event);
}
}
var element = document.getElementById('test');
var test = new EventTest(element);
$(document).ready(function() {
$("#test").on('myevent', function(e) {
console.log('myevent', e);
});
});
What I want is to create a CustomEvent in pure Javascript, enrich it with some properties and trigger that event so it can be cached also by a library like jQuery.
As you can see in the fiddle, the CustomEvent is triggered well and it is actually populated with custom properties - but when it reaches jQuery on() the custom properties is gone from the first level. My custom properties is now demoted to e.originalEvent.xyz and so on.
That is not very satisfactory. I want at least my own properties to be at the first level.
Also, in a perfect world, I would like to get rid of most of the standard properties in the dispatched event, so it contained (theoretically optimal) :
e = {
xyz : 'abc',
customData : 'test'
}
Is that possible at all? If so, how should I do it?
I have run into the same issue, couple of months ago, the point is:
When an event is received by jQuery, it normalizes the event properties before it dispatches the event to registered event handlers.
and also:
Event handlers won't be receiving the original event. Instead they are getting a new jQuery.Event object with properties copied from the raw HTML event.
Why jQuery does that:
because it can't set properties on a raw HTML event.
I had decided to do the same, I started to do it with a nasty way, and my code ended up so messy, at the end I decided to use jQuery.trigger solution, and pass my event object as the second param, like:
$("#test").bind("myevent", function(e, myeventobj) {
alert(myeventobj.xyz);
});
var myobj = {"xyz":"abc"};
$("#test").trigger("myevent", myobj);
for more info check this link out: .trigger()
Related
I have already looked at these questions:
How to find event listeners on a DOM node when debugging or from the JavaScript code?
can I programmatically examine and modify Javascript event handlers on html elements?
How to debug JavaScript/jQuery event bindings with Firebug (or similar tool)
however none of them answers how to get a list of event listeners attached to a node using addEventListener, without modifying the addEventListener prototype before the event listeners are created.
VisualEvent doesn't display all event listener (iphone specific ones) and I want to do this (somewhat) programmatically.
Chrome DevTools, Safari Inspector and Firebug support getEventListeners(node).
You can't.
The only way to get a list of all event listeners attached to a node is to intercept the listener attachment call.
DOM4 addEventListener
Says
Append an event listener to the associated list of event listeners with type set to type, listener set to listener, and capture set to capture, unless there already is an event listener in that list with the same type, listener, and capture.
Meaning that an event listener is added to the "list of event listeners". That's all. There is no notion of what this list should be nor how you should access it.
Since there is no native way to do this ,Here is the less intrusive solution i found (dont add any 'old' prototype methods):
var ListenerTracker=new function(){
var targets=[];
// listener tracking datas
var _elements_ =[];
var _listeners_ =[];
this.init=function(){
this.listen(Element,window);
};
this.listen=function(){
for(var i=0;i<arguments.length;i++){
if(targets.indexOf(arguments[i])===-1){
targets.push(arguments[i]);//avoid duplicate call
intercep_events_listeners(arguments[i]);
}
}
};
// register individual element an returns its corresponding listeners
var register_element=function(element){
if(_elements_.indexOf(element)==-1){
// NB : split by useCapture to make listener easier to find when removing
var elt_listeners=[{/*useCapture=false*/},{/*useCapture=true*/}];
_elements_.push(element);
_listeners_.push(elt_listeners);
}
return _listeners_[_elements_.indexOf(element)];
};
var intercep_events_listeners = function(target){
var _target=target;
if(target.prototype)_target=target.prototype;
if(_target.getEventListeners)return;
if(typeof(_target.addEventListener)!=='function'||typeof(_target.removeEventListener)!=='function'){
console.log('target=',target);
throw('\nListenerTracker Error:\nUnwrappable target.');
}
// backup overrided methods
var _super_={
"addEventListener" : _target.addEventListener,
"removeEventListener" : _target.removeEventListener
};
_target["addEventListener"]=function(type, listener, useCapture){
var listeners=register_element(this);
// add event before to avoid registering if an error is thrown
_super_["addEventListener"].apply(this,arguments);
// adapt to 'elt_listeners' index
var uc=(typeof(useCapture)==='object'?useCapture.useCapture:useCapture)?1:0;
if(!listeners[uc][type])listeners[uc][type]=[];
listeners[uc][type].push({cb:listener,args:arguments});
};
_target["removeEventListener"]=function(type, listener, useCapture){
var listeners=register_element(this);
// add event before to avoid registering if an error is thrown
_super_["removeEventListener"].apply(this,arguments);
// adapt to 'elt_listeners' index
useCapture=(typeof(useCapture)==='object'?useCapture.useCapture:useCapture)?1:0;
if(!listeners[useCapture][type])return;
var lid = listeners[useCapture][type].findIndex(obj=>obj.cb===listener);
if(lid>-1)listeners[useCapture][type].splice(lid,1);
};
_target["getEventListeners"]=function(type){
var listeners=register_element(this);
// convert to listener datas list
var result=[];
for(var useCapture=0,list;list=listeners[useCapture];useCapture++){
if(typeof(type)=="string"){// filtered by type
if(list[type]){
for(var id in list[type]){
result.push({
"type":type,
"listener":list[type][id].cb,
"args":list[type][id].args,
"useCapture":!!useCapture
});
}
}
}else{// all
for(var _type in list){
for(var id in list[_type]){
result.push({
"type":_type,
"listener":list[_type][id].cb,
"args":list[_type][id].args,
"useCapture":!!useCapture
});
}
}
}
}
return result;
};
};
}();
ListenerTracker.init();
EDIT
Suggestion from #mplungjan: modified to listen to wrappable targets (singleton|constructor). 'init' tracks Element and window .
exemple with other wrappable target:
ListenerTracker.listen(XMLHttpRequest);
Suggestion from #kodfire : You may get optionals arguments with the args property.
I can't find a way to do this with code, but in stock Firefox 64, events are listed next to each HTML entity in the Developer Tools Inspector as noted on MDN's Examine Event Listeners page and as demonstrated in this image:
You can obtain all jQuery events using $._data($('[selector]')[0],'events'); change [selector] to what you need.
There is a plugin that gather all events attached by jQuery called eventsReport.
Also i write my own plugin that do this with better formatting.
But anyway it seems we can't gather events added by addEventListener method. May be we can wrap addEventListener call to store events added after our wrap call.
It seems the best way to see events added to an element with dev tools.
But you will not see delegated events there. So there we need jQuery eventsReport.
UPDATE: NOW We CAN see events added by addEventListener method SEE RIGHT ANSWER TO THIS QUESTION.
Using jQuery I need to:
persists list of all event handlers that are added to element,
remove them all for few seconds and
return things to initial state (reassign the same event handlers)
I found that get list of current listeners with (some jQuery inner mechanisms):
var eventsSubmitBtn = $._data(submitButton[0], "events");
Then I can remove all event listeners with
submitButton.off();
But last stem seems not to be working
setTimeout(function () {
$._data(submitButton[0], "events", eventsSubmitBtn);
}, 5000);
eventsSubmitBtn is an empty array.
Is this the way this should be done with initial setting and I'm need something like deep cloning for those objects or this can't be done with $._data?
N.B. I have possibility to add my cistom code after all other system js code, thus I can't place the code assigning to $.fn.on before anything. Code that I write will run the last on startup and other event listeners are attached before my scripts will run.
As you get a reference to the object returned by $._data(), any change to that object will not go unnoticed, i.e. after you invoke .off(), that object will have changed to reflect that there are no handlers attached any more.
You could solve this by taking a shallow copy of the object, (e.g. with Object.assign).
But this is not really a recommended way to proceed. According to a jQuery blog, "jQuery._data(element, "events") ... is an internal data structure that is undocumented and should not be modified.". As you are modifying it when restoring the handlers, this cannot be regarded best practice. But even only reading it should only be used for debugging, not production code.
It would be more prudent to put a condition in your event handling code:
var ignoreEventsFor = $(); // empty list
$("#button").on('click', function () {
if (ignoreEventsFor.is(this)) return;
// ...
});
Then, at the time it is needed, set ignoreEventsFor to the element(s) you want to ignore events for. And when you want to revert back to normal, set it to $() again.
Now adding this to all your event handlers may become a burden. If you stick to using on() for attaching event handlers, then you could instead extend $.fn.on so it will add this logic to the handlers you pass to it.
The following demo has a button which will respond to a click by changing the background color. With a checkbox you can disable this from happening:
/* Place this part immediately after jQuery is loaded, but before any
other library is included
*/
var ignoreEventsFor = $(), // empty list
originalOn = $.fn.on;
$.fn.on = function (...args) {
var f = args[args.length-1];
if (typeof f === 'function') {
args[args.length-1] = function (...args2) {
if (ignoreEventsFor.is(this)) return;
f.call(this, ...args2);
};
}
originalOn.call(this, ...args);
}
/* This next part belongs to the demo, and can be placed anywhere */
$(function () {
$("#colorButton").on('click', function () {
// Just some handler that changes the background
var random = ('00' + (Math.random() * 16*16*16).toString(16)).substr(-3);
$('body').css({ backgroundColor: "#" + random });
});
$("#toggler").on('change', function () {
// Toggle the further handling of events for the color button:
ignoreEventsFor = $(this).is(':checked') ? $("#colorButton") : $();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="colorButton">Change color</button><br>
<input type="checkbox" id="toggler">Disable events
Notice: the above code uses ES6 spread/rest syntax: if you need support for IE then that would have to be written using the arguments variable, apply, ...etc.
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:
What i'm trying to do is a combination of a mootools class and raphael. The problem i got is mainly mootools event binding i guess.
I'm trying to append an event to a raphael element (dom node) and when firing the event another class method should be called.
This is no problem when coding without a mootools class. But this (the right) way i have some problems. When binding the events, the raphael element cannot be longer used because "this" now refers to the mootools class.
Please take a look at this code and i guess you will understand what my problem is:
// mootools class
var test = new Class({
...
initPlane: function() {
// just an JSON object array
this.objects = [{"pid":"2","sx":"685","sy":"498","dx":"190","dy":"540"},{"pid":"3","sx":"156","sy":"341","dx":"691","dy":"500"}];
// place the objects on stage and append some events to them
this.objects.each(function(item, idx){
item.gfx = this.gfx.image("assets/img/enemy.png", item.sx, item.sy, 32, 32);
// #### differnt approaches to bind the events. all not working
// first attempt with mootools event
item.gfx.node.addEvent('click', function(e) {
console.log(this.attr('x')); // not working because this is bound to the class i guess
this.info();
}.bind(this));
// second attempt with mootools event
item.gfx.node.addEvent('click', function(e) {
console.log(this.attr('x')); // not working
parent.info(this); // no binding and not working
});
// first attempt with raphael event
item.gfx.click( function(e) {
console.log(this.attr('x')); // works !
this.info(this); // not working because this refers to raphael element.
});
}.bind(this))
},
// this method should be called after click event and output element attribs
info: function(event) {
console.log(event.attr('x'));
},
...
});
your .each is wrong.
Object.each(obj, function(el, key, obj) {
}, bind);
http://mootools.net/docs/core/Types/Object#Object:Object-each
although you actually have this.objects as array, did not notice :)
Array.each(function(el, index) {
}, bind);
when you need this to be bound to element on click, that's fine. just store a copy of this into self and call self.info() instead. alternatively, keep the bind and reference e.target as the trigger element instead, whilst this is your instance
although it may seem 'neater' to try to keep this bound to the class wherever possible, mootools-core devs tend to prefer the var self = this; way as it avoids the extra callback to bind etc (look at the mootools source, very common)
also, say you want to have the click event go to a method directly:
element.addEvent("click", this.info.bind(this));
which will send the event as the 1st argument to info (so reference event.target).
bind can usually apply arguments as well as the scope (depending on the implementation), and that allows you to write function (raphaelObj, node) { ... }.bind(null, this, item.gfx.node) to bind two arguments.
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