I was wondering, what is the purpose of CustomEvent, because it can be easily emulated by good old Event.
So, what is the difference between:
var e = new Event("reload");
e.detail = {
username: "name"
};
element.dispatchEvent(e);
and
var e = new CustomEvent("reload", {
detail: {
username: "name"
}
});
inner.dispatchEvent(e);
Why does CustomEvent exist if it is easy to attach custom data to ordinary Event object?
It's not the same. You can't set the detail of a real CustomEvent:
var event = new CustomEvent('myevent', {detail:123});
event.detail = 456; // Ignored in sloppy mode, throws in strict mode
console.log(event.detail); // 123
var event = new Event('myevent');
event.detail = 123; // It's not readonly
event.detail = 456;
console.log(event.detail); // 456
Yes, you could use Object.defineProperty. But I guess the point is that the argument of CustomEvent was supposed to set some internal data of the event. Now it only considers detail, which is not used internally. But a future spec could add something new, and then you may not be able to set that internal data by using properties.
A CustomEvent also inherits from CustomElement.prototype. That only adds detail and the deprecated initCustomEvent. But you can add your own methods or properties in there, which won't be inherited by other events. But I don't recommend this, you shouldn't modify objects you don't own.
So basically you can use CustomEvent in order to classify the event differently than other events. See this graphic from an old spec
Related
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:
Being a long time C++/C# developer, I find myself moving a lot of of my JS code into "classes" to group functions and data together. As those classes handle event though, I'm finding myself having to write "stub" handlers that serve only to route the calls into a class method to provide the proper this context. So I'm doing things like this:
var Manager = {
foo: 'x',
bar: 1,
onClickStub: function(evt) {
// 'this' refers to HTMLElement event source
Manager.onClick(evt);
},
onClick: function(evt) {
// 'this' now refers to Manager.
// real work goes here.
}
}
Is this the normal way of doing things or is there a better way to structure my event handlers while keeping my class organization?
As Joseph Silber said in the comments above, I think bind would be perfect in this case. If you need to support older browsers, you can always add a shim to Function.prototype.bind (see an example implementation in the MDN docs). Then your code could just be:
var Manager = {
var foo: 'x',
var bar: 1,
// no more stub!
onClick: function(evt) {
// 'this' will refer to Manager.
// real work goes here.
}
}
// And when you bind the event handler:
var el = document.getElementById('something');
el.addEventListener('click', Manager.onClick.bind(Manager));
The best way that I know to do this is to assign this to another variable at the top of your class and then refer to that one throughout the class. But of course this only works if you are not using an anonymous object as your class.
For instance:
var Manager = function(){
var self = this,
var foo = 'x',
var bar = 1;
var onClick = function(evt) {
console.log(self); // refers to the manager
console.log(this); // refers to the element the onclick is assigned to
// If you want this to equal the manager then just do: this = self;
}
}() // edit: to make this an immediate function
In response to a comment below you could attach the onclick like this
element.onclick = Manager.onClick;
Then in this case the this variable in the onclick function is indeed the html element, and the self variable is the Manager function.
I have Javascript that people are including in their page. In my Javascript I have a version of jQuery (1.8 for sake of easy reference) that is sectioned off into its own namespace, and referenced via a global variable (but not one of the two default vars of "$" or "jQuery"). This allows users to have jQuery in their page and have it not interfere with the stuff I'm doing internally in my functions.
So we have one page that has jQuery already (1.4), and everything works fine, except that the user and my code are both listening to "click" events on elements, and theirs is going first, so on the few events they do that return false, jQuery stops propagation and my event never gets triggered. I need my event to go first. The user is expecting my onClick functionality to still work.
Now I know that jQuery keeps its own order of events internally through the _data() object, and through this it is possible to unbind existing events, bind my event, then rebind the existing events, but that only applies to objects bound through that instance of jQuery. I'd rather not just blindly look for the jQuery object in hopes that the conflict was introduced by a user's own version of jQuery. After all what happens when a user binds the event not through jQuery? Trying to manipulate the existing jQuery object in the page isn't a good solution.
I know that, depending on browser, they are using addEventListener/removeEventListener or attachEvent/detachEvent. If only I could get a listing of the already added events, I could rebind them in the order I wanted, but I can't find out how. Looking through the DOM via Chrome inspect I don't see onclick bound anywhere (not on the object, not on window or document either).
I'm having the darndest time trying to figure out just exactly where jQuery binds its listening. To be able to control the order of its own events, jQuery must blanketly listen somewhere and then fire off its own functions right? If I could figure out where that's done I might get some insight into how to ensure my event is always first. Or maybe there's some Javascript API I haven't been able to find on Google.
Any suggestions?
We solved this by just adding a little jQuery extension that inserts events at the head of the event chain:
$.fn.bindFirst = function(name, fn) {
var elem, handlers, i, _len;
this.bind(name, fn);
for (i = 0, _len = this.length; i < _len; i++) {
elem = this[i];
handlers = jQuery._data(elem).events[name.split('.')[0]];
handlers.unshift(handlers.pop());
}
};
Then, to bind your event:
$(".foo").bindFirst("click", function() { /* Your handler */ });
Easy peasy!
As Bergi and Chris Heald said in the comments, it turns out there's no way to get at the existing events from the DOM, and no method to insert events "first". They are fired in the order they were inserted by design, and hidden by design. As a few posters mentioned you have access to the ones added through the same instance of jQuery that you're using via jQuery's data, but that's it.
There is one other case where you can run before an event that was bound before your code ran, and that's if they used the "onclick" HTML attribute. In that case you can write a wrapper function, as nothingisnecessary pointed out in a rather over-the-top toned comment below. While this wouldn't help in the instance of the original question I asked, and it's now very rare for events to be bound this way (most people and frameworks use addEvent or attachEventListener underneath now), it is one scenario in which you can solve the issue of "running first", and since a lot of people visit this question looking for answers now, I thought I'd make sure the answer is complete.
I encounter an opposite situation where I was asked to include a library, which uses event.stopImmediatePropagation() on an element, to our website. So some of my event handlers are skipped. Here is what I do (as answered here):
<span onclick="yourEventHandler(event)">Button</span>
Warning: this is not the recommended way to bind events, other developers may murder you for this.
Its not a proper solution, but ... You can add event handler to parent node in capture phase. Not on target element itself!
<div>
<div id="target"></div>
</div>
target.parentNode.addEventListener('click',()=>{console.log('parent capture phase handler')},true)
Third argument in addEventListener means:
true - capture phase
false - bubble phase
Helpful links:
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
https://javascript.info/bubbling-and-capturing
Found it easiest to add addListener and removeListener methods to document (as that's only where I need them - I suppose you can use Element.prototype and this instead). Only one "real" listener is added per type, and it's just a func to call the actual listeners in order. The eventListeners dictionary is added to document (so can mess with the handler or order).
[edit]
I think the correct answer for most cases is to use the 3rd argument of addEventListener: https://stackoverflow.com/a/29923421. The answer below ignores the argument (on purpose).
[edit] Updated code to only add one extra property: document.eventHandlers + modified naming.
// Storage.
document.eventListeners = {}; // { type: [ handlerFunc, listenerFuncs ] }
// Add event listener - returns index.
document.addListener = (type, listener, atIndex) => {
// Get info.
const listening = document.eventListeners[type];
// Add to existing.
if (listening) {
// Clean up.
atIndex = atIndex || 0;
const listeners = listening[1]; // Array of funcs.
// Already has.
const iExists = listeners.indexOf(listener);
if (iExists !== -1) {
// Nothing to do.
if (iExists === atIndex)
return atIndex;
// Remove from old position.
listeners.splice(atIndex, 1);
}
// Add (supporting one cycle of negatives).
const nListeners = listeners.length;
if (atIndex > nListeners)
atIndex = nListeners;
else if (atIndex < 0)
atIndex = Math.max(0, atIndex + nListeners + 1);
listeners.splice(atIndex, 0, listener);
}
// New one.
else {
// Handler func.
const handler = (...args) => {
const listening = document.eventListeners[type];
if (listening) {
const listeners = listening[1]; // Array of funcs.
for (const listener of listeners)
listener(...args);
}
};
// Update dictionary.
document.eventListeners[type] = [ handler, [ listener ] ];
// Add listener.
document.addEventListener(type, handler);
// First one.
atIndex = 0;
}
// Return index.
return atIndex;
};
// Remove event listener - returns index (-1 if not found).
document.removeListener = (type, listener) => {
// Get info.
const listening = document.eventListeners[type];
if (!listening)
return -1;
// Check if exists.
const listeners = listening[1];
const iExists = listeners.indexOf(listener);
if (iExists !== -1) {
// Remove listener.
listeners.splice(iExists, 1);
// If last one.
if (!listeners.length) {
// Remove listener.
const handlerFunc = listening[0];
document.removeEventListener(type, handlerFunc);
// Update dictionary.
delete document.eventListeners[type];
}
}
// Return index.
return iExists;
}
Aliaksei Pavlenkos suggestion about useCapture can be used. His allegation that it must be attached to the parent node is wrong: MDN
Event listeners in the “capturing” phase are called before event listeners in any non-capturing phases
target.addEventListener(type, listener, useCapture);
Just so it's said, I think this might be possible if you override the native implementations of these functions. This is BAD practice - very bad practice when developing a library to alter native implementations, because it can easily conflict with other libraries.
However, for completeness, here's one possibility (completely untested, just demonstrating the general concept):
// override createElement()
var temp = document.createElement;
document.createElement = function() {
// create element
var el = document.createElement.original.apply(document, arguments);
// override addEventListener()
el.addEventListenerOriginal = el.addEventListener;
el._my_stored_events = [];
// add custom functions
el.addEventListener = addEventListenerCustom;
el.addEventListenerFirst = addEventListenerFirst;
// ...
};
document.createElement.original = temp;
// define main event listeners
function myMainEventListeners(type) {
if (myMainEventListeners.all[type] === undefined) {
myMainEventListeners.all[type] = function() {
for (var i = 0; i < this._my_stored_events.length; i++) {
var event = this._my_stored_events[i];
if (event.type == type) {
event.listener.apply(this, arguments);
}
}
}
}
return myMainEventListeners.all[type];
}
myMainEventListeners.all = {};
// define functions to mess with the event list
function addEventListenerCustom(type, listener, useCapture, wantsUntrusted) {
// register handler in personal storage list
this._my_stored_events.push({
'type' : type,
'listener' : listener
});
// register custom event handler
if (this.type === undefined) {
this.type = myMainEventListeners(type);
}
}
function addEventListenerFirst(type, listener) {
// register handler in personal storage list
this._my_stored_events.push({
'type' : type,
'listener' : listener
});
// register custom event handler
if (this.type === undefined) {
this.type = myMainEventListeners(type);
}
}
// ...
A lot more work would need to be done in this regard to truly lock this down, and again, it's best not to modify native libraries. But it's a useful mental exercise that helps to demonstrate the flexibility JavaScript provides in solving problems like this.
In a Node.js-based project, I have a couple objects:
EmitterA
EmitterB
Consumer
In Consumer's constructor, I have the following:
var a = new EmitterA();
a.on('someEvent', someEventHandler);
In EmitterA's constructor, I have the following:
var self = this;
var b = new EmitterB();
b.on('someEventFromB', function() {
self.emit('someEvent');
});
Finally, in EmitterB's constructor, I have something like this:
this.emit('someEventFromB');
The problem is that Consumer's someEventHandler() doesn't get fired when the inital .emit() from EmitterB is fired. The reason is one of timing. Consumer doesn't subscribe to someEvent on EmitterA until after the event has been fired.
Is there a way to attach event handlers at the time of construction? I have considered utilizing the newListener event, but this will be problematic when working with multiple listeners later on, and situations where there are many different types of events that will be fired. I'd like to avoid this route if possible, and am looking for alternatives that you may suggest. Thanks!
try something like:
var a = new EmitterA({
events : {
someEvent : someEventHandler
}
});
function EmitterA( props ){
props || ( props = {} );
if( props.events ){
for( var n in props.events ){
this.on( n, props.events[n] );
}
}
// the rest of the constructor code
}
I just thought of an alternate way that works for my specific case, but may be problematic for others. Please use it with a grain of salt.
In EmitterB's constructor, I changed this.emit('someEventFromB'); to this:
process.nextTick(function() {
this.emit('someEventFromB');
}
See also: http://howtonode.org/understanding-process-next-tick
I'm looking to build two functions:
var MyObject = new Object();
MyObject.MyProperty = 1;
function ListenToChange() {
here I want to listen to changes in MyProperty1 and do something when it changes
}
function ThrowEvent () {
here I change the value of MyProperty every 5 seconds
setTimeOut('ThrowEvent', 5000);
}
I looked at the addEventListener property but it looks like it works for DOM objects. I thought of using the ThrowEvent function to change the value of a hidden div and listen for the changes in the value of the hidden with $('#HiddenDiv').change() but I'm wondering if there's a better way to do it with the addEventListener.
Thanks for your help.
I can infer from your example you're using jQuery; you could trigger a custom event anytime you change the value of your property:
var my_obj = {my_prop: 1}
function ListenToChange(event, newval) {
console.log('my_prop is now' + newval)
}
$.bind("propchange:my_prop", ListenToChange)
function ThrowEvent () {
$.trigger('propchange:my_prop', my_obj.my_prop)
setTimeOut('ThrowEvent', 5000);
}
Well here is an example of what I came up with. It is not nearly as glamorous as John's example, but it gets the job done. Recommendations for improvements are more than welcome.
http://jsfiddle.net/yKYRs/
var MyObject = new Object();
MyObject.MyProperty = 1;
MyObject.MyProperty_Set = function(val) {
this.MyProperty = val;
ListenToChange();
}
function ListenToChange() {
console.log('My Property was changed');
}
MyObject.MyProperty_Set(2);
It seems that you are after ES5 set and get, however they won't work in an ECMAScript Ed 3 environment, you'll need to write your own getter and setter functions for that.
If you want to listen for changes to the DOM, there is a W3C DOM 2 Events specification that includes mutation events, however I don't think it was ever widely implemented. There is a level 3 working draft that deprecates a number of the level 2 features.