Javascript this if function caller is object from DOM - javascript

I have a button with onclick method which calls function.
What is not clear to me, why is this inside function seen as Object? Isn't this suppose to be object calling method? In this case it is the button?
I pass only one parameter as this to the method. And this parameter correctly shows button (named parameter element inside function).
Why is this inside function not showing button inside DOM which called the method?
var test = (function() {
var test1 = {
nameT: 'test'
};
methodTesting(test1);
function methodTesting(element) {
debugger;
}
return {
methodTesting: methodTesting
}
});
<button onclick="test.methodTesting(this);" data-itest=1 data-ctest2='miran' data-ct='feri'>TEst</button>

It's not a reference to the element because you're assigning it via the onclick attribute.
Use addEventListener instead or get a reference to the element via the currentTarget property of the Event.

Related

How is inline onclick evaluated?

I am curious about how the content of the inline element attribute event works under the hood.
We start off with a simple function
function handler(e) {
console.log(e);
}
Use Case 1
If we would want to add this handler to our button we could do:
<button onclick="handler();">Click Me</button>
Which Logs on click: undefined
Fair enough, we called a function without passing arguments.
Use Case 2
If we now change the button to take the event as an argument:
<button onclick="handler(event);">Click Me</button>
We log on click: MouseEvent{ ... }
Now we log the event, as we could have expected
Use Case 3
But what if we just specify the function name?
<button onclick="handler">Click Me</button>
Nothing happens!, I was hoping just giving a reference to a function, would do the trick. So when "onlick" would happen, the referred function would be called with the proper event argument.
Use Case 4
When we add the handler in javascript:
const btn = document.getElementById('btn');
btn.onclick = handler;
it works, capturing the event and logs the related event object.
Concluding Question
Whatever text/content/statement that we pass to onclick="" inline, does it basically run eval() on it? And when we attach it in javascript (btn.onclick = ...) it goes through the proper process to intercept the event and then call bounded handler function and passes the corresponding event object with it (e.g. MouseEvent)?
Quick Note
To be clear, this is an informative question, to understand the fundamentals, disregarding if it's considered a bad or good practice. That's not being discussed at this point.
Yes, in an inline handler, the attribute text is essentially just evaled, with a few qualifications. What
<button onclick="handler">Click Me</button>
is roughly equivalent to would be if you had a (true) handler which referenced the handler function but didn't call it, for example:
btn.onclick = () => {
handler;
};
This doesn't throw an error (because handler is referenceable), but it doesn't run handler either, because you're only referencing the function, not calling it.
When you do
btn.onclick = handler;
you are passing a reference to handler to the onclick internals, which then call that reference later, when the button is clicked.
(If you had done btn.onclick = handler;, handler would be invoked immediately, and its return value would be added as a listener, which isn't what you'd want)
When you do
<button onclick="handler(event);">Click Me</button>
the event argument you're passing comes from window.event, which is why it's referenceable.
Like I said, there are a few qualifications, though: inline handlers implicitly use something like a with(this) around the whole text that is run, where this is the element in question. (it also uses with for the document and containing form, as you can see from Quentin's answer) For example:
<a onclick="search();">Click me!</div>
looks, to the interpreter, a bit like:
<a onclick="
with(this) {
search();
}
">Click me!</div>
which is problematic, because search is a property of HTMLAnchorElement.prototype. Referencing other variable names can have similar problems, if those variable names are properties anywhere on the element's prototype chain.
Best to avoid inline handlers entirely, and attach the event properly using Javascript instead.
This is not what happens, but you can imagine it like this:
Imagine that the javascript engines saves the content of the onclick attribute as a string, and when a user clicks the element, then evaluates the content.
For example:
function foo(a, b) {
alert(a + ' ' + b);
}
<button onclick="foo('hello', 'world')">click me</button>
<button onclick="(function () { alert ('I\'m an inline IIFE') })()">click me</button>
It's the same result of eval("foo('hello', 'world')". What if you pass event or this or other keywords?
Well, also here, you can imagine a javascript engines doing this:
var this = /* create context */;
var event = /* create event */;
eval("foo(this, event)");
This is what the specification says, the attribute needs to be evaluated! I'm not saying that the engine uses eval :P
Example:
<button onclick="alert('hello world')">Click me</button>
What about btn.onclick = handler;?
This is another pair of shoes. The onclick property is a callback, and it wants a function reference.
When you use HTML attribute for the event, you actually create a function under the hood which assigned as the handler of the relevant event.
The body of the function is the content of the attribute. It should be JavaScript code.
The function gets as parameter the event parameter
So,
In case 1, The generated handler :
function handlerHtml(event) {
handler()
}
In case 2, The generated handler :
function handlerHtml(event) {
handler(event)
}
In case 3, The generated handler :
function handlerHtml(event) {
handler
}

Type error: "Not a function" using "this" keyword in a function

I have created the following function.
function showAllSelectOpts(select)
{
selectLength = select.children().length;
select.attr('size',selectLength);
select.css('height','auto');
select.focusout(function(){
select.attr('size','1');
});
}
When it is called directly on a select element like this showAllSelectOpts(mySelect); it works fine, but when called within another function, as below using the keyword "this", it returns the error. Type error: select.children not a function
$('select').on('focus',function(){
showAllSelectOpts(this);
})
Is this a scope issue or what, and how can I resolve it?
In an event handler, this is a reference to the DOM element, not a jQuery object. But your showAllSelectOpts expects its argument to be a jQuery object.
Either change the call to wrap the DOM element with $():
showAllSelectOpts($(this));
...or update showAllSelectOpts to do so itself:
function showAllSelectOpts(select)
{
select = $(select); // ***
selectLength = select.children().length;
select.attr('size',selectLength);
select.css('height','auto');
select.focusout(function(){
select.attr('size','1');
});
}
Side note: As A.Wolff points out, your function attaches a new focusout handler to the select every time it's called. You only want one.
I'd remove that part of the handler entirely, and replace it with a single focusout:
function showAllSelectOpts(select)
{
var selectLength = select.children().length;
select.attr('size',selectLength);
select.css('height','auto');
}
$('select')
.on('focus',function(){
showAllSelectOpts($(this));
})
.on('focusout', function(){
$(this).attr('size', '1');
});
Also note that I added a var for selectLength in showAllSelectOpts (although actually, you could just remove the variable entirely); without one, the code is falling prey to The Horror of Implicit Globals (that's a post on my anemic little blog). Be sure to declare your variables.
jQuery event listener callbacks set this as the HTMLElement that the event was fired on.
In your callback you are expecting a jQuery object, but you have the HTMLElement.
You can pass the HTMLElement to a jQuery constructor and pass it into the showAllSelectOpts function
$('select').on('focus',function(){
showAllSelectOpts($(this));
})
Try this one -
$('select').on('focus',function() {
showAllSelectOpts($(this)); })
Try this:
var myselect = $('select');
$('select').on('focus',function(){
showAllSelectOpts(myselect);
})
A better way could be:
$('select').on('focus',function(event){
showAllSelectOpts($(event.target));
})
Why your code not working?
$('select').on('focus',function(){
//Here `this` is bound with the dom html element, not the $('select') object.
showAllSelectOpts(this);
})
These previous answers fix it. I'd just add here to create it as an extension since $(this) refers to a prototype of one method call.
$.fn.showAllSelectOpts=function() {
$(this).on('focus',()=>{
$(this)
.css('height','auto')
.attr('size',$(this).children().length)
.focusout(()=>{
$(this).attr('size','1');
});
});
};
$('select').showAllSelectOpts();

How to set a property of an element to contain a function using jQuery?

I want to assign a function to a custom attribute of a DOM-element. Using jQuery.
.prop(String, function) will not assign the function to the specified property, but will invoke the function and assign its result.
I have an ugly solution:
$("#a").prop(
"customAttr",
function() {
return function() {
$("#a").text("B");
};
}
);
http://jsfiddle.net/rapik/fyn6zh85/
Is there any better way to do it (using jQuery)?
I'm guessing that the reason you're trying to associate this function with these objects is so that you can cause the function to be invoked when something specific happens in the application. A better way to do this is to use events.
// set up the event handler
$("#a").on('somecustomevent', function() { $(this).text("B"); });
// Then, where you want that function to be invoked, trigger it.
$("#a").trigger('somecustomevent');

Using Trigger on body

I have a variable being set in a plugin JS file, and in my own custom JS file, I'm calling the same event to piggypack on the developers work.
The issue is I need to access a variable set in the plugin javascript event. It's currently being set as follows in the plugin JS:
$('#link').live('click', function() {
var test = 123456;
var another = 'string';
$('body').trigger({
'test' : test,
'another' : another
});
});
Now, in my own custom JS, I'm calling the same method on the #link to add my own code to execute.
$('#link').on('click', function(){
// I need to access the another variable set previously
});
What I don't really get is when the jquery docs say that the trigger method is passing arguments to the event handler. What does that mean exactly? How would I access the variable set in a previous event?
Thanks in advance as always.
NORMALLY, you would pass extra parameters to an event, IF it has a custom event, you can access that:
$('#link').live('click', function() {
var test = 123456;
var another = 'string';
$('body').trigger('HIT',{
'test': test,
'another': another
});
});
$('body').on('HIT', function(e,param1) {
$('#hitter').append('hi');
$('#hitter').append(param1.test);
});
after this HIT event, the value of #hitter would have "hi123456" appended to it. To access the "another" is simply param1.another, which returns "string".

Getting correct "this" in jQuery button callback

I have a class:
function RustEditor() {
this.init = function() {
var saveButton = this.container.find("button.saveButton");
saveButton.click(function(){this.save();});
};
...
When I click the button, it complains that this.save is not a function. This is because "this" does not refer to the instance of RustEditor here, but to the button. What variable can I use inside that callback closure to point to the instance of RustEditor? I could use rust.editor (it's name in the global scope) but that's smelly code.
Common practice is to enclose the this value like so:
function RustEditor() {
this.init = function() {
var self = this;
var saveButton = this.container.find("button.saveButton");
saveButton.click(function(){self.save();});
};
Update with suggestion from tvanfosson:
this gets rebound when the event handler is invoked and thus you need to capture the reference to the class at the time the object is created with a variable that will retain that reference in the closure.
Within RustEditor() you could first copy a reference to the button and use that.

Categories