I was looking at this basic example below (which makes all images in the DOM semi-transparent on mouseover), and was confused as to how an arbitrary function, such as handleMouseOver, receives an event object if you give it an argument.
How is it that the act of assigning such a function to the onmouseover attribute tells it to modify this function in this way, as there's nothing inherent in the function definition itself that says: "please pass me an event"? Is the assignment operator being overloaded somehow? Or is the browser doing some extra work here? I would really appreciate a link to a detailed explanation of this phenomenon because it doesn't seem to make any sense looking at it as pure JavaScript (to me at least!)
function handleMouseOver(e) {
e.target.style.opacity = 0.5;
}
function handleMouseOut(e) {
e.target.style.opacity = 1;
}
var elements = document.getElementsByTagName("img");
for (var i = 0; i < elements.length; i++) {
elements[i].onmouseover = handleMouseOver;
elements[i].onmouseout = handleMouseOut;
}
Lets break it down by taking one browser's example. IE'S OnMouseOver Event for instance.
In the remarks section it says it passes IHTMLEventObj for ALL events even for the events that don't require it such as Body.OnLoad.
When we go into IHTMLEventObj's detail, we read the following remarks
Although all event properties are available to all event objects, some properties might not have meaningful values during some events
So, Event object is passed regardless; you have to access the object in some specific events and get event-specific properties to get event-related data.
onmouseover, for example, is an event handler. When the event handler needs to be called (in this case when the browser javascript engine decides it) then it will call it be passing it some pre-determined arguments (all good documentation will explain what those arguments are). Your use of those arguments is optional however.
This can be demonstrated with a manual function call like so:
function myFunction(e){
alert(e.myProperty);
}
//assign the handler
var handler = myFunction;
//when required, create event parameter data and call the function assigned to the handler
var myE = { myProperty: "some data" };
handler(myE);
It is not "exactly" how it works (because I don't know how browsers have chosen to implement their code), but it shows the concept.
Here is an example in action
Not only the event object is passed, but also the this value within the function is set to the event target. This is done by the browser, and dictated by the DOM specification.
EDIT:
I was hoping to find something more detailed in the DOM specification (I'm sure I've seen that before), but so far I found this:
In JavaScript, user-defined functions are considered to implement the EventListener interface. Thus the Event object will be provided as the first parameter to the user-defined function when it is invoked. Additionally, JavaScript objects can also implement the EventListener interface when they define a handleEvent method.
https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#glossary-event-handler
By the way, the last sentence is talking about an interesting way to bind event listeners, in an OO context.
You can pass whatever arguments you like to any JavaScript function.
Defining them in the function definition just means you have a named, local variable to access them with.
That is to say:
function foo() {
}
foo("hello");
… won't throw an error.
When a function is treated as an event handler (which is what code provided by the browser will do if you assign a function to the onmouseover property of a DOM node) then the event object will be passed as an argument.
Related
In JavaScript I'm attempting to set an onchange callback that accepts a parameter. However, how I'm currently doing it overrides the event object that is created. I don't actually need the event for my purposes, but I would like to know how I can capture both the event and any passed in parameters in case my needs change.
EDIT: For clarity, this onchange event could be called both programatically and by a user. There may be an instance where I'm creating an empty select element so the user can pick what they want, or creating a populated one based on some other interaction.
EDIT: Also, the below is a simplified example of a much larger code base. Assume that the scoping is not global of any of the variables. I'm really looking for an answer of how to specifically be able to capture both and event object (when called via user interaction) and another object (when called via code). It feels like having the atr parameter mean different things in different contexts is hacky - but I come more from a strongly typed background so it might be just me.
function update(atr) {
...
}
document.getElementById("myelement").onchange = update;
var atr = {"id":1,"param":"val1"};
// This gives me atr in the function as defined above
document.getElementById("myelement").onchange(atr);
// This way, however, gives me atr in the function as the event
document.getElementById("myelement").onchange();
What I would really like is something like this:
function update(e, atr) {
// Now I have e as the event and atr as the value I've passed in
}
document.getElementById("myelement").onchange = update;
var atr = {"id":1,"param":"val1"};
// This gives me atr in the function as defined above
document.getElementById("myelement").onchange(atr);
However the above code doesn't work. I suspect that I have to do something with bind() but as far as I can understand that I would simply be overriding the event's (this) object in the function like I'm doing now implicitly.
The accepted answer in this question Similar Question is basically what I want to do, but that is with React JS and I would like to do this without any frameworks. I've been trying to search for multiple parameters and onchange events and primarily getting React or unrelated responses. Either this is a harder question than I think, or I'm searching for the answer in completely the wrong way.
I will explain what happens in the linked answer as you mentioned that you want to achieve the same behaviour.
So:
<fieldset onChange={(e) => this.props.handleChange("tags", e)}>
This React code attaches anonymous function with one parameter e to the fieldset as onChange listener. This function in its body invokes another function, passing e with additional parameters.
Translating this into your code, you would like to achieve something like this:
function update(e, attr) {
// e is instance of Event
// attr is additional parameter
}
document.getElementById("myelement").onchange((e) => update(e, attr));
// or without ES6 arrow function:
document.getElementById("myelement").onchange(function(e){ update(e, attr); });
Also, be advised that proper way of attaching event listeners is by addEventListner API.
I'm not sure I understand exactly what you're trying to do, but first you need to distinguish between the case that the event is triggered by the user and the case that you call the event programatically, when you call it programatically there is no event.
you can do something like this:
You mentioned that you use select, the logic is that when a change in the select occurs the event is thrown and you get the selected value, in your case the value can be the content of the atr var:
HTML
<select id="myelement" onchange="update(event)">
<option value='{"id":1,"param":"val1"}'>val1
<option value='{"id":2,"param":"val2"}'>val2
</select>
JavaScript
function update(e) {
var atr = JSON.parse(document.getElementById("myelement").value);
//now you have access both to the event and the 'parameter'
}
This covers the case when the event is triggered by the user, when you want to trigger the event programatically, since there is no event, use a different function that take the atr parameter.
I started learn JavaScript and in some tutorials I saw that was used document.getElementById() and in some tutorials in same case was used "this" that was passed from control that fired the event.
Maybe someone can explain why should I prefer one way over an other?
Thank's a lot
Are you referring to the documentation from this MDN page on addEventListener? Make sure you look at the section on the value of this within the handler. The earlier examples use document.getElementById to look up an arbitrary element. For example, you could attach a click handler to a button (or any other element), that modifies some other element when it is clicked. In this case, you don't really do anything with the object that generated the click event. So in the handler, you will use document.getElementById to modify the element you want.
However, in some other cases you would like to modify the element itself. For example, you might want to change the color of an element the user clicks on, to let them know that its state has changed (a toggle basically). In this case, you want access to the element that actually generated the event, and you can do that by using this. Here this means "I am the HTML element who generated this event".
In a more general context, this can have different meanings, and it depends on the use case. In the context of event handlers, this means the element that generated the event. In other cases, such as in object-oriented JavaScript, this in a function can refer to the parent object of that function. JavaScript is flexible enough that a function can have whatever this value the author decides. In well-written code, this has a meaning that generally makes sense, and usually refers to some sort of "owning" context or object.
Functions assigned as Events have their this context as the Object to which the Event belongs. In other words, the Element itself.
// you can get Element a number of ways including document.getElementById('idHere')
function someFunc(){
this.style.color = 'blue';
}
Element.onclick = someFunc;
or
Element.addEventListener('click', someFunc);
or
Element.onclick = function(){
this.style.color = 'blue';
}
or
function someFunc(context){
context.style.color = 'blue';
}
Element.onclick = function(){
someFunc(this);
// more code here
// could use document.getElementById('whatever') to get HTML Element with id='whatever'
}
I have an object that generates HTML elements that are also connected with an array of the object, and let us say we have one instance of it. So as it creates the elements it also assigns the following event listener to a nested part of the element (the class being uploadDelete).
Now this event listener needs to call the delete method of the instance of the object that created it, with the value of i assigned at its creation. Because events are under Window, the instance needed to be passed to an anonymous function along with the i value.
This therefore assigns a very unique function to the event, and because the delete method will be destroying the element containing the listener I would like to remove it first; from what I've read it could cause leaks otherwise(?). I'm also using Strict Mode, so not arguments.callee.
file.display.getElementsByClassName('uploadDelete')[0].addEventListener('click',
(function(that,i){
return function() {
that.delete(i);
};
})(this,i), false);
I've tried many different things, but when I started having an anonymous function inside of a function inside of a function which is then called in the listener, I figured I should just ask on here. I might have a solution to the overall problem, changing other code, but it would still help if this could be answered.
Here is what I ended up doing, with the help of Norguard's answer. Since the uniqueness was stemming from an object called file, I just created a new property of file to store the function:
file.deleteFunction = (function(that,i){
return function() {
that.delete(i);
};
})(this,i);
file.display.getElementsByClassName('uploadDelete')[0].addEventListener('click',file.deleteFunction, false);
The delete function that is called then removes the event listener.
A relatively-painless way of doing this might be to create an object in the scope that's responsible for adding and deleting listeners, which builds an ID, serial or non, and will store whatever the listener is in an object, with that ID, returning the ID to whichever object/module requested it (or passing the anonymous function back to them).
// trivial example
var listeners = {},
i = 0,
add = function (context, func, closure) {
var enclosed = (function (closure) {
return function () { /* ... */; func(); };
}(closure)),
index = i;
context.addEventListener("...", enclosed, false);
listeners[index] = enclosed;
i += 1;
return index;
};
add will now add your listener, but will also store the function that you're passing into addEventListener into the listeners object.
If you need a reference to i, you've already got it in the closure, if you want it.
So now when you remove stuff, you can just look for the function saved at listeners[i].
An alternate, if you don't want to save a table full of these in one spot, for whatever reason, would be to catch the return statement, and instead of returning i, return the function;
// inside of your module
// I'm not usually crazy about `this`, without doing something particular with it,
// but hopefully it illustrates my point
this.cached_func = add(this.el, this.callback.bind(this), this.secret);
So now, when it comes time to delete everything, and you want to shut down your listener...
remove(this.cached_func);
All of that said, the leaks that you've read about are still possible, but the major culprit was IE6/7 (and earlier).
As people steer further from bad browsers, this becomes less important.
In fact, encouraging memory-dumps in IE6 is probably just a good way to encourage people to not use IE6.
After reading related questions #1 , #2
I still haven't found an answer to the following question:
Javascript can set context (i.e. set this) with: bind , call and apply.
But when I'm write an event handler:
document.getElementById('myInput').onclick = function ()
{
alert(this.value)
}
Who/What actually attaches this to the object itself ?
P.S. When using jQuery's :
$("#myInput").bind(function (){...})
there is an internal implementation of (bind, call or apply)
So when I am not using jQuery, who is doing it?
Why, the DOM/JavaScript of course, it's supposed to work that way by W3C.
Event handlers are invoked in the context of a particular object (the current event target) and are provided with the event object itself.
Source
How exactly that happens, we don't know. It's an implementation detail.
All we know is, that the semantics as defined by the W3C are achieved in some way, but which part of the browser does that and and how, that is left up to the browser developers, and they can implement it as they see fit.
To sum up all the discussions:
In general it is JavaScript that binds this to o inside a function call, when o.x() is called.
However, there are some alternative methods of calling functions (like f.apply() and f.call()) that change this behaviour.
onclick is a special case, and the method used to invoke it is unknown and depends on the DOM implementation.
The answers saying it is the DOM are wrong.
This is part of JavaScript itself, as a language. The DOM is ONLY what the name indicates "Document Object Model", which is just how HTML is represented for manipulation by using JavaScript. Objects related to the DOM follow the behavior specified by the standards, but this is implemented by using JS for it. It is the JS engine what does this, in communication with whatever layout engine is being used (Gecko, Trident, WebKit, Presto, etc.). So, if WebKit detects an event, it passes it to the JS engine as the DOM specification indicates so that it can manipulated by the JS programmer (which is why you're even asking about this, because you can work with it).
In other words, if you're writing something in JavaScript, the only engine that understands how to read and execute that is the JS engine. This engine (v8, SpiderMonkey/Jugger/Trace) will receive data from the layout engine and use it so that you can interact with it. Similarly, on the other hand, whenever you run code that affects the layout, the changes will be detected by the layout engine and it will change the layout so that the user perceives the changes: even if the JS code might have initiated this, it is the layout engine that takes care of the layout.
What "this" is when you assign a function to an object, is simply wherever the function belongs to. So, if you assign a function to instance of object a, then said function will refer to a whenever you use "this" inside of it.
If you wanted to think of it in implementation terms, think of it this way:
Whenever you are calling a method, you do so by first telling an instance that you want to call a method with N parameters. This instance calls the method but adds itself into the context, as "this".
In Python this is done more explicitly by making the first parameter of all instance methods the instance itself. Here it is the same, but the instance is passed implicitly instead of explicitly.
Remember, the instance owns the method. When you do "document.getElementById('something')" the call returns an object (which happens to be an HTMLElement object that is part of the DOM, but that's coincidental to how JS interacts with the DOM), and then you are assigning the function as the property click.
Then, whenever you call the method, the JavaScript engine passes the instance by default, just like it passes other variables (like arguments is also generated without you doing anything, also done by the JS engine which implements the ECMAScript standard).
I would recommend checking pages 63:
"The this keyword evaluates to the value of the ThisBinding of the current execution context."
but most importantly, page 68 "Function calls"
http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
In your example, of an onclick handler it's perfectly straight forward: a DOM element is an object, you're defining the onclick property to be a function. That function effectively becomes a method of that DOMElement/object.When that object is clicked, the function is called as a method of that element, so this points to its owner, the element.
Put simply, the context in which the function executes is the same as the context in which is was created (again: in your example as a method of a DOM Element), unless a reference to a function object is assigned to another object, or when that function object is invoked in another context using call or apply & co.There's a little more to it than this, of course: as I hinted at above, functions are objects themselves and are said to be loosely coupled to their "owner". Well, actually they don't have an owner as such, each time a function is called, its context is determined:
var foo = someObject.someFunction;//reference to method
someObject.someFunction();//this === someObject, context is the object preceding the function
foo();//implies [window].foo(); ==> this is window, except for strict mode
As #wroniasty pointed out, my talking about ownership might be slightly confusing. The thing is, functions are objects, they're not owned by anything. When an object is assigned a method, all that object really owns is a reference to a given function object. When that function is called via that reference, this will point to the object that owned the calling reference. When we apply that to your elem.onclick = function(){}, we see the element only owns a reference to a function expression that was declared in some scope (global, namespace-object, doesn't matter). When the click event fired, that reference will be used to call the handler, thus assigning a reference to the element to this. To clarify:
document.getElementById('foo').onclick = (function()
{//This function returns the actual handler
var that = this;//closure var
console.log(this);//logs window object
//defined in global context
return function(e)//actual handler
{
console.log(this === that);//false
console.log(this);//elem
console.log(that);//window
};
})();//IIFE
So the handler was declared in the global context, and the handler can access its the context it was declared in using that, thanks to closures (but that's another story). The point is, the event references the handler using the onclick property of the element foo. That property is a reference to a function object, so the function object sets its context to whatever object made the call.
I do hope this clears up any confusion I caused with regard to ownership of functions, and perhaps how context in JS is determined.
http://dmitrysoshnikov.com/ecmascript/chapter-3-this/#this-value-in-the-function-code
Basically, it's done by JavaScript internals.
The context is the object calling the function, e.g.
elem.onclick(); // elem === this
However:
func = elem.onclick;
func() // global === this
This really has nothing to do with the DOM as has been mentioned, but how JavaScript is designed to work when you call a function within an object.
Take this as an example:
var myObject = {
id: 1,
onclick: null
}
myObject.onclick = function() {
console.log(this.id);
}
Calling myObject.onclick() will log 1 to the console, which means myObject is its context.
Since onclick is also a property of an object, this will be the parent object, in your case an HTMLElement.
For illustration purposes, although implementations may differ, think of the following function
function f() { alert(this.name); }
as
function f(this) { alert(this.name); }
Imagine this as a secret parameter that you can override with bind, apply and call but that normally gets set to the calling object by the browser.
Example
var a = {},
b = {};
a.name = "John";
b.name = "Tom";
// "this" param added secretly
function printName( ) {
console.log( this.name )
};
a.printName = printName
b.printName = printName;
When calling the printName function the browser sets that "secret" this parameter to the calling function. In the example below this is b and so "Tom" is printed to the console.
printName( ); // global context assumed so this === window
b.printName( ); // this === b and outputs "Tom"
printName.call( a ); //this === a and outputs "John"
Further info here.
If I have some event handlers registered inline with my markup (deprecated, I know) like
span id="..." onclick="foo(p1,p2,p3)"
how can I access the "event" object in the event handler function foo? Changing the above to
span id="..." onclick="foo(event,p1,p2,p3)"
and then using it in foo like:
function foo(e,p1,p2,p3)
{
if (!e) e = window.event;
}
seems to work but I don't see it documented anywhere so I am wary of using it. In other words, is the first parameter to a inline event handler always the event object if it is named as such in the onclick=... markup? Is this cross-browser so it can be safely used? And if it is not named as such (as in my first example), the parameters are treated like regular parameters and the event object is not passed?
Thoughts?
You're misunderstanding your code.
The string that you put in the inline handler is a normal piece of Javascript code. It does not need to be a single function call; it can even contain multiple statements (separated by semicolons, as usual)
The code in the inline handler will be given a variable called event which refers to the event object.
When you write onclick="foo(event,p1,p2,p3)", you're making a regular function call, and passing the values of four variables named event, p1, p2, and p3 as parameters to the function.
Take a look here. This seems to line up with your example. However, there is some mention of this not working the same way in IE, so you have to check whether the first argument (event object) is defined and if not use window.event.
Another reference here. I frequently find MDC to be helpful.
Ok, so I ran a few tests in Firefox (3.5.8/linux) and here's what I've come up with. I was unaware of 'event' being used like in example 2, but it seems to work correctly in Firefox. However, it is NOT the case that the first variable passed to a function is always the event. 'event' seems to be registered in some global object, but I can't seem to determine which one. (It's not document, or window :P)
The line of code that you have in the foo function
if (!e) e = window.event;
is basically how you have to catch events in Internet Explorer anyway, so it will work in IE and Firefox for sure. And yes, therefore, if you are not passing a variable called 'event', as in your second example, the parameters will be treated as normal parameters, and the event object will not be passed.