Given a generic handler bound to a series of links with YUI, how do I find out which link triggered the event?
YUI().use('node', function (Y) {
var list = Y.one('#studentList'), links;
links = list.all('a');
links.on('click', function (e) {
alert(this.get('id')); // this just shows a comma delimited list of all ids
});
});
I suppose I could bind each link individually instead of using the "on" idiom on the links list, but it seems odd to me that YUI would not provide access to the DOM node. Digging into the event object shows several private fields that look like the DOM node, but surely there must be a safe way of doing this.
e.currentTarget appears to be what you're looking for:
links.on('click', function (e) {
alert(e.currentTarget.get('id'));
});
From NodeList's on:
By default, the this object will be the NodeList that the subscription came from, not the Node that received the event. Use e.currentTarget to refer to the Node.
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.
I use jqInlineEdit for inline editing on a web page. Everything works, except I don't know how to get the id of the item which I need for saving the change to the database(via Django).
The HTML looks like this:
<div id="remark14756" class="remark" data-cid="14756">
Sample Text
</div>
That's the JavaScript:
<script src="/static/inline-edit.jquery.js"></script>
<script>
$(".remark").inlineEdit({
type: 'textarea',
onChange: function (e, text, html) {
// Executes when exiting inline edit mode and a change has been made
c_id = $(this).attr("data-cid");
alert("Test: ", c_id)
}
});
</script>
Obviously, $(this) does not work in this context. I tried everything and searched a lot but I can't find how to do it the right way. Does anybody know the answer?
The inlineEdit docs say:
onChange(this, text, html) - Executes when exiting inline edit mode and a change has been made
with the use of this being quite misleading.
therefore the first param is actually the Element.
$(".remark").inlineEdit({
type: 'textarea',
onChange: function (elem, text, html) {
// `this` refers to inlineEdit instance plugin
// `elem` is the currently edited element
const c_id = $(elem).attr("data-cid");
alert(c_id); // 14756
}
});
That plugin is not performing in an expected "jQuery Plugin" way.
Usually properly written plugins should:
bind all methods to the Element callee,
(in case of Event methods) the first parameter should always refer to the original Event.
allowing a developer to reference it using the this keyword to get the native JS Element or either doing $(this) inside the exposed public Methods just like we're expected from native jQuery Methods, and to have accessible the Event (i.e: useful in case we use arrow functions to extract the currentTarget since the inexistence of this keyword)
$someElem.on('click', function(evt) {
const $el = $(this); // what we're used to
});
$someElem.on('click', (evt) => {
const $el = $(evt.currentTarget); // the importance of always passing the Event as first param
});
clearly not implemented in that plugin.
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()
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