Simple event system using JavaScript (ES5) - JS Custom Event - javascript

Instructions:
make this code work without modifying snippet it in any way.
Use only plain old JavaScript and no third-party libraries.
Write new code that enables the code below to work properly.
Hint: Feel free to extend native objects... even though it's typically a bad practice.
// Start with an object, any object
var myObject = {};
// Register an event on your object using
// an `on` method
myObject.on('myEvent', function(data) {
// Log the data passed to the callback
console.log(data);
});
// Trigger the event using a `trigger` method.
// Include some data when you trigger the event.
myObject.trigger('myEvent', {
company: 'ABC Corp',
location: 'WTC Bangalore, IN',
website: 'http://abc.co'
});
// Register a different event
myObject.on('yourEvent', function() {
console.log('yourEvent fired');
});
// Trigger the new event
myObject.trigger('yourEvent');
// Trigger all existing events using a special
// "star" identifier.
myObject.trigger('*');
// Remove one event by name
myObject.off('myEvent');
// Since we've removed the event, this should
// do nothing
myObject.trigger('myEvent');
// Remove all existing events
myObject.off();
// Since we've removed all events, this should
// do nothing
myObject.trigger('*');
Everything else went well. I'm unable to get "arguments" while implementing myObject.trigger("*"); unable to read arguments object / parameters while implementing "*" and hence throw undefined.
My JSFiddle

Disclaimer
I obviously dont know what school you go to or anything, but please don't fool yourself trying to fool your teachers. With a few simple questions they'll know if you understand the material or not, and if you show up with a good answer but no knowledge to back it up, they will know what's up. I'm not accusing you of this, just a friendly word of advice of someone who has had good connections with his teachers after graduating last year ;)
So, how do we do this? Basically, you will have to add some functionality to the prototype of object, at least if you want this to affect all objects made afterwards. You can always create your own class and add the function to that prototype if you only want that class to have this functionality.
We need 3 functions added to the prototype, on, off and trigger of course. On top of that we add one extra property called events, initially an empty object.
You can look at the raw code for all these in the jsfiddle, I will only go through the structure and logic of the code here.
events will hold all the handlers (functions) associated with each event. When adding an event for the first time, we add a eventName property to the events object, the value for this property is initially an empty array.
on will find (or create) the array linked to eventName in events, and push the function into the array (note we do not call the function at this time, we simply store the reference to the function in the array).
off will iterate the array of eventName, and if it finds the same function (note the ===), remove it from the array.
trigger will iterate the array of eventName and call each function. Note that the function is called with the this keyword in the function set to the object, and with the same parameters as the trigger function was called (except eventName, the first parameter, which is filtered out). Yes that means you can pass as many parameters as you want to trigger(), and they will all be passed to each handler.
I won't go into detail what things like splice, slice, ===, arguments and apply do exactly, I'm sure you can find more and better information about that elsewhere on the world wide interwebs.
There's a lot more you can do for this, like making the events object invisible through some nice uses of scoping, but that wasn't part of the question so I didn't bother with that.
If you have any more questions after looking through this, feel free to ask. I also didn't test it extensively so if you find any bugs, let me know.
EDIT: I didn't read through the comments at first, but I now also added support for the '*' wildcard. Basically the functions now check for the wildcard and will iterate all eventNames on the event object when removing or triggering. You can also remove all functions for an event by not giving a function or by giving the same wildcard, but with an eventName.
EDIT2: had some bugs running the teacher's code, realized I forgot to check for hasOwnProperty while iterating. Look that one up, it's very important when working with prototypes!
I now put in the teacher's code in my jsfiddle, to show you that it works :)
jsfiddle with own code
jsfiddle with teacher code
EDIT3 - about the 'undefined' log.
The teacher's code calls .trigger 5 times, and you should see 4 console logs and as far as I can tell, they are all correct.Let me run through each trigger, and the subsequent console logs.
You add a handler to myEvent, which logs the first parameter
You trigger myEvent, with parameter => The parameter (the object), is
logged.
You add a handler to yourEvent, which logs a hardcoded
string.
You trigger yourEvent, no parameter => The hardcoded string is logged'
You trigger * with no parameter, all handlers run => undefined is logged, since no parameters were given, data in myEvent's handler is undefined. The hardcoded string is also logged
You remove the myEvent handler, trigger myEvent and confirm no functions are called
You remove all event handlers, trigger * and confirm no functions are called from any events.
I honestly don't know what you expected to happen on step 5, since you give no parameter, the data is assigned undefined, that's intended behaviour.
If you want to merge the data given in step 2 so it remains on the object, then instruct so in your handler. (for example, iterate all properties of data and add them to this, then log this). Right now you simply pass it data, it gets logged, and then thrown away. You can also add a parameter in step 5, and then all handlers will receive it (including the yourEvent handlers, but that one doesn't assign nor use it).

document.getElementById("myBtn").addEventListener("click", displayDate);

Related

Multiple functions on click prevents function with event parameter - Vue

If I have multiple functions passed to a click event i.e.
#click="
inputHandler();
sendToken(computedUser.email);
responseMessage();
"
The function with an event parameter:
inputHandler(e) {
// code
}
Won't run. If I pass it on it's own:
#click="inputHandler"
It works fine.
Why is this and how can I get around it?
Internally Vue uses some RegExps to decide what form of event handler you're using.
If it seems to be the name of a method it will call it and pass the event.
If it seems to be inline code it'll just run it. In this case the event object is accessible as $event.
So:
#click="inputHandler($event)"
is roughly equivalent to:
#click="inputHandler"
Strictly speaking these aren't quite the equivalent, for example with component events that emit multiple arguments you'll only get the first one using $event like this.
See https://v2.vuejs.org/v2/guide/events.html#Methods-in-Inline-Handlers
For a deeper understanding see the Vue source code:
https://github.com/vuejs/vue/blob/0baa129d4cad44cf1847b0eaf07e95d4c71ab494/src/compiler/codegen/events.js#L96
Give your eyes a few minutes to adjust and it isn't too difficult to understand.
Personally I try to avoid anything more complicated than a single method call in the inline listener. Instead I'd suggest having something like #click="onSendClick" and let the method onSendClick worry about the details.
If I recall correctly, vue creates a wrapper function, if the passed value isn't a function. So
inputHandler();
sendToken(computedUser.email);
responseMessage();
actually get's turned into
function wrapper(){
inputHandler();
sendToken(computedUser.email);
responseMessage();
}
And as you can see the arguments passed to wrapper are lost.
The easiest way to fix this is probably to create a new method that accepts the event parameter and calls all of your function and use that one in the event handler.

Is attaching an onchange object a closure?

I have searched prior SO posts here, here and here, and couldn't an answer that made sense to me. This should be a basic question, but I'm not understanding the posts I find. They don't seem to address using a this parameter.
I want to programatically add an input with an onchange event, such that the final result is this:
<input type="button" onchange="handleButtonOnChange(this)">ClickMe</input>
I am working on a project that is using an embedded IE6 browser inside a old Delphi application, so I have to have a solution that is IE6 compatible (yes, IE6 is horrible, but there are reasons I am stuck with it for now).
My initial attempt was this:
var DaySelect = document.createElement("select");
DaySelect.id = ParentID+"-day";
DaySelect.disabled = true;
MonthSelect.onchange="handleDayChange(this);" //<--- not correct
Parent.appendChild(DaySelect);
I then read that the .onchange should be assigned an object, not a string, and one should use this instead:
MonthSelect.onchange=handleDayChange; //<--- '(this)' removed
But it seem to me that this will result in this element (notice the missing this parameter)
<input type="button" onchange="handleButtonOnChange">ClickMe</input>
If I use the line below, instead, won't this make a closure, and the 'this' will refer to the event at the time the object is assigned to the .onchange property, instead of being the event at the time of the change event?
//Does the line below make a closure?
MonthSelect.onchange=handleDayChange(this); //<-- What does 'this' refer to?
I'm a relatively new web programmer, but long time Delphi programmer. Closures still make my head hurt. I appreciate any help in this.
Also, I read here about using addEventListener and the problems with older versions of IE, and the last post on the page provides a work around. But I don't understand how it works.
EDIT -- And what about passing other parameters? It seems that many event handlers will need to have parameters specific for the attached element. It seems that it is just not possible to add a listener with any parameters.
A simple closure if you are creating the elements in JS as you show:
var DaySelect = document.createElement("select");
DaySelect.id = ParentID+"-day";
DaySelect.disabled = true;
MonthSelect.onchange=function(){handleDayChange(DaySelect);};
Parent.appendChild(DaySelect);
Since the function is created inside the scope that you create the element in, the same variables will be available to it.
EDIT:
Additional parameters can be passed with this method, for example, the anonymous function we create and attach as the handler will still have the event object sent to it:
function(e){handleDayChange(DaySelect, e);};
In the event object you will have access to the event target, but in your example the event target and "this" are not the same element, so there would be no way for the handler to know about the DaySelect element.
jQuery makes a lot of event handling much simpler which is one of the reasons many people use it, it also normalizes it's methods between various browsers so you don't have to write multiple versions of the same code (in most cases)

Backbone.js collection.remove documentation explanation

Can someone please clearly explain how to interpret the documentation for the collection.remove() method in backbone.js as seen here:
http://backbonejs.org/#Collection-remove
The docs show collection.remove(models, [options]), but I don't understand what this means for the actual usage of the function. What does options represent here? I'm confused because the callback also has an options argument as seen here:
http://backbonejs.org/#Events-catalog
The callback docs say:
"remove" (model, collection, options) — when a model is removed from a collection.
This means if I call collection.remove(model) somewhere in my code, my callbacks will be passed the collection, the model, and an options parameter. Is this options parameter the same one that can optionally be passed in on the remove call itself. If so, what are these options supposed to be used for?
If you would read the links that you posted, you will see this
"Generally speaking, when calling a function that emits an event (model.set, collection.add, and so on...), if you'd like to prevent the event from being triggered, you may pass {silent: true} as an option. Note that this is rarely, perhaps even never, a good idea. Passing through a specific flag in the options for your event callback to look at, and choose to ignore, will usually work out better."
So you can use the options to prevent the remove event from firing. You can also use it (as mentioned in the last sentence) to pass option values to the callback. So yes, anything you pass in as options to the remove function will be passed through as the options to the remove event.

Javascript event handler order

I have an input field, which has two event handlers bound to it.
Validate & AutoSave
Obviously I want to validate before I save. If validation fails, the "invalid" class is added to the input and autosave will check for that class before it proceeds.
This works well enough, but is there a way to guarantee Validate runs before Autosave in all cases?
If you use JQuery to bind your events, it guarantees that handlers are fired in the same order that they were bound. Otherwise the order is officially undefined.
If you cannot use JQuery or a similar framework you can easily simulate this by using your own custom even binding, where your generic handler is a function which keeps an array of functions and calls them in order.
Normally you'd have the Save event handler call Validate() which will return true if everything is fine and ready to be saved.
function onSaved() {
if (!validate()) {
// set class
return;
}
// do the save
}
Why not attach just one handler -- Validate -- and call AutoSave from inside it?
For an answer to your question that isn't also a question, see this post or this one or this one.
Already answered - but just to add this piece of knowledge, the order of event handlers can not be relied upon. It may in any given implementation be predictable, but this can change from one (Javascript) implementation to the next and/or over time. The only thing certain is that they all will be executed - but not in what order.
Note that the situation is similar when there is an event handler for a DOM object and another one for the same event for a child or parent - which of those is executed first is not always clear as well. See http://www.quirksmode.org/js/events_order.html

Are jQuery events automatically unbound upon destruction of what they where bound to?

If I have the following code in two functions of an object:
add: function()
{
// create trip.
var trip = new Trip();
// add the trip using its id.
this.trips[trip.id] = trip;
},
remove: function(tripId)
{
// remove trip.
delete this.trips[tripId];
}
NOTE: The constructor for the Trip object binds a bunch of custom jQuery event handlers to itself.
Will the event handlers bound to the Trip object be automatically destroyed/cleaned up when the Trip object is deleted?
Would the same occur for a DOM node if it was removed and had event handlers bound to it?
Also I read that objects are not cleaned up by the garbage collector until all references to them no longer exist, so do the event handlers bound to the object by itself count as references and prevent the object from being cleaned up, even when I am no longer referencing it?
The event will not be deleted as jQuery maintains a central repository of all bound event handlers, and does know if or when you deleted an associated object using delete. Try this little test to confirm. (jQuery 1.4.2 only)
jsfiddle link:
// 1. a regular JS object
var root = {};
// Since we can't delete anything created with var (except on Chrome),
// we use an object property here. An array value works just as well,
// which is already the case in your example.
root.o = {};
// 2. at this point, jQuery creates an internal property
// jQuery<UNIQ_ID>, for example jQuery1277242840125 inside object o
$(root.o).bind("myEvent", function() { alert("here"); });
// 3. get the *internal* index jQuery assigned this object:
// there's only 1 property, so we just enumerate and fetch it.
var internalIndex;
for(var prop in root.o) {
internalIndex = root.o[prop];
}
// 4. delete the object
delete root.o;
// 5. query jQuery internal cache with the internal index from step 3
console.log(jQuery.cache[internalIndex].events);
​
Step 5 should log an array of all event types that were associated with the ex-o object, including "myEvent", and it's associated handler, so no the bound events handlers will not delete automatically. Here's what I see get logged (stripped out irrelevant properties):
▾ Object
▾ myEvent: Array (1)
▾ 0: Object
▸ handler: function () { alert("here"); }
namespace: ""
type: "myEvent"
length: 1
The object deletion, however, is not affected, and that will be deleted as expected. However, it is a hole in the wall kind of a situation since there is associated data somewhere in jQuery's cache that will remain there.
It seems that although you can bind events to plain JavaScript objects, you cannot unbind them. It appears jQuery assumes the object is a DOM node when unbinding, and throws the following error:
Uncaught TypeError: Object #<an Object> has no method 'removeEventListener'
Even the part about being able to bind events to objects is, I believe, undocumented. So you have to be a little careful on this, as the following will not work when trying to clean up the event handler references for that object:
$(object).remove()
$(object).unbind(..)
As a workaround, when cleaning up the Trip object, you can explicitly call removeData to do the job.
$(object).removeData();
As I've already mentioned, it's getting knee-deep with jQuery's internals, so you might want to look at an alternative solution, or be wary that library upgrades can easily break your code, which is not very unlikely.
As far as I know, you can only bind event handlers to nodes, or, in special cases, the window, document, etc. For DOM nodes, the event handlers will be removed. Even if they weren't, they wouldn't be able to be triggered anyway. Deleting the object will remove the event handlers associated with it. The event handlers should not prevent the object from being garbage collected.
Would the same occur for a dom node if
it was removed and had event handlers
bound to it?
this.handlerClick = function () { ... };
$(this.testDomNode).bind('click', this.handlerClick);
this.testDomNode.parentNode.removeChild(this.testDomNode);
Using the above code and testing with FireQuery in FireFox removing the dom node does not unbind the handler from the event,
it seems you have to explicitly unbind the handler before removing the dom node as follows:
$(this.testDomNode).unbind('click', this.handlerClick);
this.testDomNode.parentNode.removeChild(this.testDomNode);

Categories