how does $(this) work when nothing is selected - javascript

I just got into caching my jquery objects but not sure how to properly do it when using (this).
Also the only time I know how to use (this) is when its inside a click object or function so like:
$(".trigger").click(function () {
if ($(this).hasClass('toggle')) {
$(this).closest("td").next().find(".clueless > div").slideUp();
$(this).removeClass('toggle');
} else {
$(this).closest("td").next().find(".clueless > div").slideDown();
$(this).addClass('toggle');
}
});
so if I wanted to cache the $(this).closest("td").next().find(".clueless > div") the only thing I could think would be:
var $something = $(".trigger").find(this).closest("td").next().find(".clueless > div");

I will not completely re-iterate how the this keyword works, but there's an exhaustive explanation here.
In JS when the default this behaviour is not altered
Keeping things simple, to know the object to which the this keyword refers to you can simply look at the left-side of the . in a function invocation.
For example, in myObj.someFunction(), the this keyword within someFunction will point to myObj (that is unless the function has been bound using Function.prototype.bind).
If the function is not invoked on an object, such as someFunction(), then this will point to the global object which is window in browsers.
This is also the case within anonymous functions that are passed around, except for addEventListener, which will make sure that the this value within the handler is the object to which the handler was attached.
E.g.
setTimeout(function () { this; /*window*/ }, 10);
document.addEventListener('click', function (e) {
e.target; //the clicked DOM element
this; //the document
});
When this is altered by the API
Using Function.prototype.call or Function.prototype.apply, it is possible to specify the object to which this will point to during a function execution.
Some libraries (e.g. jQuery) are taking advantage of that feature to make this point to an object that is more intuitive, rather than the global object.
E.g.
$('#someEl').on('click', function (e) {
this; //the DOM element that was clicked (not the jQuery wrapper)
});
When this is altered in such way by the library, there is no other way than looking at the library's documentation to see what this will be.
We can read from jQuery event docs that:
In addition to the event object, the event handling function also has
access to the DOM element that the handler was bound to via the
keyword this.
Rewriting your function
Now, here's how you could re-write your function:
$(".trigger").click(function () {
var $this = $(this).toggleClass('toggle'),
$elementToSlide = $this.closest("td").next().find(".clueless > div"),
isToggled = !$this.hasClass('toggle'),
slideBehavior = isToggled? 'slideUp' : 'slideDown';
$elementToSlide[slideBehavior]();
});

Not sure exactly what you're trying to achieve but you can cache $(this) whenever it has context.
$(".trigger").click(function () {
var $trigger = $(this);
if ($trigger.hasClass('toggle')) {
$trigger.closest("td").next().find(".clueless > div").slideUp();
$trigger.removeClass('toggle');
} else {
$trigger.closest("td").next().find(".clueless > div").slideDown();
$trigger.addClass('toggle');
}
});

If you want to cache $(this).closest("td").next().find(".clueless > div") then
$(".trigger").click(function () {
var el = $(this).closest("td").next().find(".clueless > div");
if ($(this).hasClass('toggle')) {
el.slideUp();
$(this).removeClass('toggle');
} else {
el.slideDown();
$(this).addClass('toggle');
}
});
I assume this is what you are going for, it would make sense to cache $(this) since you are using it multiple times
var $this = $(this);
would do it

Related

Am I missing something basic, or is there a bug with Chrome? [duplicate]

I've created a Javascript object via prototyping. I'm trying to render a table dynamically. While the rendering part is simple and works fine, I also need to handle certain client side events for the dynamically rendered table. That, also is easy. Where I'm having issues is with the "this" reference inside of the function that handles the event. Instead of "this" references the object, it's referencing the element that raised the event.
See code. The problematic area is in ticketTable.prototype.handleCellClick = function():
function ticketTable(ticks)
{
// tickets is an array
this.tickets = ticks;
}
ticketTable.prototype.render = function(element)
{
var tbl = document.createElement("table");
for ( var i = 0; i < this.tickets.length; i++ )
{
// create row and cells
var row = document.createElement("tr");
var cell1 = document.createElement("td");
var cell2 = document.createElement("td");
// add text to the cells
cell1.appendChild(document.createTextNode(i));
cell2.appendChild(document.createTextNode(this.tickets[i]));
// handle clicks to the first cell.
// FYI, this only works in FF, need a little more code for IE
cell1.addEventListener("click", this.handleCellClick, false);
// add cells to row
row.appendChild(cell1);
row.appendChild(cell2);
// add row to table
tbl.appendChild(row);
}
// Add table to the page
element.appendChild(tbl);
}
ticketTable.prototype.handleCellClick = function()
{
// PROBLEM!!! in the context of this function,
// when used to handle an event,
// "this" is the element that triggered the event.
// this works fine
alert(this.innerHTML);
// this does not. I can't seem to figure out the syntax to access the array in the object.
alert(this.tickets.length);
}
You can use bind which lets you specify the value that should be used as this for all calls to a given function.
var Something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // undefined, as this is the element
};
this.onclick2 = function(event) {
console.log(this.name); // 'Something Good', as this is the binded Something object
};
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2.bind(this), false); // Trick
}
A problem in the example above is that you cannot remove the listener with bind. Another solution is using a special function called handleEvent to catch any events:
var Something = function(element) {
this.name = 'Something Good';
this.handleEvent = function(event) {
console.log(this.name); // 'Something Good', as this is the Something object
switch(event.type) {
case 'click':
// some code here...
break;
case 'dblclick':
// some code here...
break;
}
};
// Note that the listeners in this case are this, not this.handleEvent
element.addEventListener('click', this, false);
element.addEventListener('dblclick', this, false);
// You can properly remove the listners
element.removeEventListener('click', this, false);
element.removeEventListener('dblclick', this, false);
}
Like always mdn is the best :). I just copy pasted the part than answer this question.
You need to "bind" handler to your instance.
var _this = this;
function onClickBound(e) {
_this.handleCellClick.call(cell1, e || window.event);
}
if (cell1.addEventListener) {
cell1.addEventListener("click", onClickBound, false);
}
else if (cell1.attachEvent) {
cell1.attachEvent("onclick", onClickBound);
}
Note that event handler here normalizes event object (passed as a first argument) and invokes handleCellClick in a proper context (i.e. referring to an element that was attached event listener to).
Also note that context normalization here (i.e. setting proper this in event handler) creates a circular reference between function used as event handler (onClickBound) and an element object (cell1). In some versions of IE (6 and 7) this can, and probably will, result in a memory leak. This leak in essence is browser failing to release memory on page refresh due to circular reference existing between native and host object.
To circumvent it, you would need to either a) drop this normalization; b) employ alternative (and more complex) normalization strategy; c) "clean up" existing event listeners on page unload, i.e. by using removeEventListener, detachEvent and elements nulling (which unfortunately would render browsers' fast history navigation useless).
You could also find a JS library that takes care of this. Most of them (e.g.: jQuery, Prototype.js, YUI, etc.) usually handle cleanups as described in (c).
Also, one more way is to use the EventListener Interface (from DOM2 !! Wondering why no one mentioned it, considering it is the neatest way and meant for just such a situation.)
I.e, instead of a passing a callback function, You pass an object which implements EventListener Interface. Simply put, it just means you should have a property in the object called "handleEvent" , which points to the event handler function. The main difference here is, inside the function, this will refer to the object passed to the addEventListener. That is, this.theTicketTable will be the object instance in the belowCode. To understand what I mean, look at the modified code carefully:
ticketTable.prototype.render = function(element) {
...
var self = this;
/*
* Notice that Instead of a function, we pass an object.
* It has "handleEvent" property/key. You can add other
* objects inside the object. The whole object will become
* "this" when the function gets called.
*/
cell1.addEventListener('click', {
handleEvent:this.handleCellClick,
theTicketTable:this
}, false);
...
};
// note the "event" parameter added.
ticketTable.prototype.handleCellClick = function(event)
{
/*
* "this" does not always refer to the event target element.
* It is a bad practice to use 'this' to refer to event targets
* inside event handlers. Always use event.target or some property
* from 'event' object passed as parameter by the DOM engine.
*/
alert(event.target.innerHTML);
// "this" now points to the object we passed to addEventListener. So:
alert(this.theTicketTable.tickets.length);
}
This arrow syntax works for me:
document.addEventListener('click', (event) => {
// do stuff with event
// do stuff with this
});
this will be the parent context and not the document context.
With ES6, you can use an arrow function as that will use lexical scoping[0] which allows you to avoid having to use bind or self = this:
var something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // 'Something Good'
};
element.addEventListener('click', () => this.onclick1());
}
[0] https://medium.freecodecamp.org/learn-es6-the-dope-way-part-ii-arrow-functions-and-the-this-keyword-381ac7a32881
According to https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener ,
my_element.addEventListener('click', (e) => {
console.log(this.className) // WARNING: `this` is not `my_element`
console.log(e.currentTarget === this) // logs `false`
})
so if you use the arrow functions you can go safe beacause they do not have their own this context.
I know this is an older post, but you can also simply assign the context to a variable self, throw your function in an anonymous function that invokes your function with .call(self) and passes in the context.
ticketTable.prototype.render = function(element) {
...
var self = this;
cell1.addEventListener('click', function(evt) { self.handleCellClick.call(self, evt) }, false);
...
};
This works better than the "accepted answer" because the context doesn't need to be assigned a variable for the entire class or global, rather it's neatly tucked away within the same method that listens for the event.
What about
...
cell1.addEventListener("click", this.handleCellClick.bind(this));
...
ticketTable.prototype.handleCellClick = function(e)
{
alert(e.currentTarget.innerHTML);
alert(this.tickets.length);
}
e.currentTarget points to the target which is bound to the "click event" (to the element that raised the event) while
bind(this) preserves the outerscope value of this inside the click event function.
If you want to get an exact target clicked, use e.target instead.
Heavily influenced by kamathln and gagarine's answer I thought I might tackle this.
I was thinking you could probably gain a bit more freedom if you put handeCellClick in a callback list and use an object using the EventListener interface on the event to trigger the callback list methods with the correct this.
function ticketTable(ticks)
{
// tickets is an array
this.tickets = ticks;
// the callback array of methods to be run when
// event is triggered
this._callbacks = {handleCellClick:[this._handleCellClick]};
// assigned eventListenerInterface to one of this
// objects properties
this.handleCellClick = new eventListenerInterface(this,'handleCellClick');
}
//set when eventListenerInterface is instantiated
function eventListenerInterface(parent, callback_type)
{
this.parent = parent;
this.callback_type = callback_type;
}
//run when event is triggered
eventListenerInterface.prototype.handleEvent(evt)
{
for ( var i = 0; i < this.parent._callbacks[this.callback_type].length; i++ ) {
//run the callback method here, with this.parent as
//this and evt as the first argument to the method
this.parent._callbacks[this.callback_type][i].call(this.parent, evt);
}
}
ticketTable.prototype.render = function(element)
{
/* your code*/
{
/* your code*/
//the way the event is attached looks the same
cell1.addEventListener("click", this.handleCellClick, false);
/* your code*/
}
/* your code*/
}
//handleCellClick renamed to _handleCellClick
//and added evt attribute
ticketTable.prototype._handleCellClick = function(evt)
{
// this shouldn't work
alert(this.innerHTML);
// this however might work
alert(evt.target.innerHTML);
// this should work
alert(this.tickets.length);
}
The MDN explanation gives what to me is a neater solution further down.
In this example you store the result of the bind() call, which you can then use to unregister the handler later.
const Something = function(element) {
// |this| is a newly created object
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // undefined, as |this| is the element
};
this.onclick2 = function(event) {
console.log(this.name); // 'Something Good', as |this| is bound to newly created object
};
// bind causes a fixed `this` context to be assigned to onclick2
this.onclick2 = this.onclick2.bind(this);
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2, false); // Trick
}
const s = new Something(document.body);
In the posters example you would want to bind the handler function in the constructor:
function ticketTable(ticks)
{
// tickets is an array
this.tickets = ticks;
this.handleCellClick = this.handleCellClick.bind(this); // Note, this means that our handleCellClick is specific to our instance, we aren't directly referencing the prototype any more.
}
ticketTable.prototype.render = function(element)
{
var tbl = document.createElement("table");
for ( var i = 0; i < this.tickets.length; i++ )
{
// create row and cells
var row = document.createElement("tr");
var cell1 = document.createElement("td");
var cell2 = document.createElement("td");
// add text to the cells
cell1.appendChild(document.createTextNode(i));
cell2.appendChild(document.createTextNode(this.tickets[i]));
// handle clicks to the first cell.
// FYI, this only works in FF, need a little more code for IE
this.handleCellClick = this.handleCellClick.bind(this); // Note, this means that our handleCellClick is specific to our instance, we aren't directly referencing the prototype any more.
cell1.addEventListener("click", this.handleCellClick, false);
// We could now unregister ourselves at some point in the future with:
cell1.removeEventListener("click", this.handleCellClick);
// add cells to row
row.appendChild(cell1);
row.appendChild(cell2);
// add row to table
tbl.appendChild(row);
}
// Add table to the page
element.appendChild(tbl);
}
ticketTable.prototype.handleCellClick = function()
{
// PROBLEM!!! in the context of this function,
// when used to handle an event,
// "this" is the element that triggered the event.
// this works fine
alert(this.innerHTML);
// this does not. I can't seem to figure out the syntax to access the array in the object.
alert(this.tickets.length);
}

Why define event handler member functions (inline) inside the constructor function in order to work with 'unbind'?

While studying the source code for a signature pad widget, I find the following code snippet inside the constructor function (note in particular the comment in the following snippet):
var SignaturePad = function (canvas, options) {
...
// MY QUESTION IS ABOUT THE FOLLOWING CODE COMMENT!!!
// v v v
// we need add these inline so they are available to unbind while still having
// access to 'self' we could use _.bind but it's not worth adding a dependency
this._handleMouseDown = function (event) {
if (event.which === 1) {
self._mouseButtonDown = true;
self._strokeBegin(event);
}
};
// ... other event handlers here
...
}
... for completeness in providing context for the above code, later the event handlers are bound as event listeners:
SignaturePad.prototype._handleMouseEvents = function () {
...
this._canvas.addEventListener("mousedown", this._handleMouseDown);
...
};
From the above code snippet, you can see the comment:
we need add these inline so they are available to unbind while still having access to 'self'
we could use _.bind but it's not worth adding a dependency`
I am scratching my head about this. Why is access self required when unbinding (and I assume by 'unbinding' is meant detaching the event listener, but please correct me if I'm wrong)?
In other words, I'd like to understand the above code comment so that I can be certain I understand the JavaScript and/or event binding thoroughly in this code.
The .addEventListener calls in that code receive a function reference when binding the handler. In order to use .removeEventListener to unbind, you need to pass a reference to the same function handler.
Because the SignaturePad constructor creates a new, unique (though identical) function for each instance, and binds that function, they need to keep a reference to that function in order to unbind later on. Therefore they put it directly on the object for later use.
The reason they create these handlers inside the constructor function is that they want them to be able to reference the SignaturePad instance that was created. So they create a var self = this variable, and have the functions created in the constructor reference self. If the handlers were on the .prototype, there would be no way for that shared handler to reference the original object, given their approach.
Here's a truncated version of their code that shows how to use the EventListener interface:
var SignaturePad = function(canvas, options) {
this._handleMouseEvents();
};
// Implements the EventListener interface
SignaturePad.prototype.handleEvent = function(event) {
switch (event.type) {
case "mousedown":
this._handleMouseDown(event)
break
case "mousemove":
this._handleMouseMove(event)
break
case "mouseup":
this._handleMouseUp(event)
break
default:
console.log("Unbound event type:", event.type)
}
}
SignaturePad.prototype._handleMouseDown = function(event) {
if (event.which === 1) {
this._mouseButtonDown = true;
this._strokeBegin(event);
}
};
SignaturePad.prototype._handleMouseMove = function(event) {
if (this._mouseButtonDown) {
this._strokeUpdate(event);
}
};
SignaturePad.prototype._handleMouseUp = function(event) {
if (event.which === 1 && this._mouseButtonDown) {
this._mouseButtonDown = false;
this._strokeEnd(event);
}
};
SignaturePad.prototype._strokeUpdate = function(event) {
console.log("stroke update");
};
SignaturePad.prototype._strokeBegin = function(event) {
console.log("stroke begin");
};
SignaturePad.prototype._strokeEnd = function(event) {
console.log("stroke end");
};
SignaturePad.prototype._handleMouseEvents = function() {
this._mouseButtonDown = false;
this._canvas.addEventListener("mousedown", this);
this._canvas.addEventListener("mousemove", this);
document.addEventListener("mouseup", this);
};
So you can see that the handleEvent method was added, and we don't actually bind any functions using .addEventListener. Instead, we bind a reference to the SignaturePad object itself.
When an event occurs, the handleEvent method is invoked with the value of this pointing our SignaturePad object we bound. We still have access to the element as well via event.currentTarget.
So this lets us reuse functions on the .prototype and gives us all the object references we need. And of course unbinding is done the same way, except that we pass the object we bound to .removeEventListener.

Mootools Element.prototype error

I want to implement some functions and variables into Element member of mootools. I have something like this
Element.prototype.currentChild = this.getFirst();
Element.prototype.scrollToNext = function(delta, tag){ .... }
After that I create a new element and bind the mousewheel event to a span and acces it's currentChild.
body_container = new Element('div', {
events:{
'mousewheel': function(e){
var elem = new Element(this);
elem.currentChild.setStyle('background-color', 'transparent');
elem.scrollToNext(e.wheel);
elem.currentChild.setStyle('background-color', '#C6E2FF');
e.stop();
}
}
});
The problem is I get the following error:
Uncaught TypeError: Object [object Window] has no method 'getFirst'
Do you know what might cause this?
LE: Yes, I was expecting 'this' to be an Element. But I can't see why it would be Window type.
use Implement to change the prototype. and you will need a function, can't say something.prototype.method = this.somethingsMethod as this is not bound outside of the execution context of the method.
Element.implement({
currentChild: function() {
return this.getFirst();
},
scrollToNext: function() {}
});
MooTools also has alias.
Element.alias('currentChild', 'getFirst');
https://github.com/mootools/mootools-core/blob/master/Source/Core/Core.js#L223-225 - aliasing on type methods, when you don't want to re-implement.
to be honest, why can't you just use element storage instead?
'mousewheel': function(e) {
var elem = document.id(this),
first = elem.retrieve('currentChild');
first || elem.store('currentChild', first = elem.getFirst());
first.setStyle('background-color', 'transparent');
elem.scrollToNext(e.wheel);
first.setStyle('background-color', '#C6E2FF');
e.stop();
}
Thanks for the quick answers. Meanwhile i found a method based on Dimitar first solution with implement. It looks like this:
Element.implement({
currentChild: function(){
if(!this._currentChild) this._currentChild = this.getFirst();
return this._currentChild;
}
}

JavaScript's Array call and functions fail inside Prototype

I have this code:
function Keyboard() {
this.log = $('#log')[0];
this.pressedKeys = [];
this.bindUpKeys = function() {
$('body').keydown(function(evt) {
this.pressedKeys.push(evt.keyCode);
var li = this.pressedKeys[evt.keyCode];
if (!li) {
li = this.log.appendChild(document.createElement('li'));
this.pressedKeys[evt.keyCode] = li;
}
$(li).text('Down: ' + evt.keyCode);
$(li).removeClass('key-up');
});
}
this.bindDownKeys = function() {
$('body').keyup(function(evt) {
this.pressedKeys.push(evt.keyCode);
var li = this.pressedKeys[evt.keyCode];
if (!li) {
li = this.log.appendChild(document.createElement('li'));
}
$(li).text('Up: ' + evt.keyCode);
$(li).addClass('key-up');
});
}
}
I get these errors:
TypeError: 'undefined' is not an object (evaluating 'this.pressedKeys.push')
It doesn't matter what I want to do with the Array, it just keeps giving me access errors, as if it doesn't exists inside the prototype.
What am I doing wrong? :( I'm just accessing the array as any other value inside the prototype). Are there problems with objects inside objects?
The problem is that inside the event handler this is not what you think. You can bind the event handler function with the bind method (or, since it looks like you're using jQuery, $.proxy):
this.bindUpKeys = function() {
var that = this;
$('body').keydown($.proxy(function(evt) {
//Use `this` as normal inside here
}, this));
}
Or you can store a reference to this outside of the event handler e.g.
this.bindUpKeys = function() {
var that = this;
$('body').keydown(function(evt) {
//Use `that` instead of `this` in here
});
}
as if it doesn't exists inside the prototype.
No, it does.
I'm just accessing the array
That's what you don't. this does not point to your Keyboard instance inside an event listener.
When the function is called as an event listener the DOM element will be the context (jQuery does that, too). See MDN's overview for the this keyword. You can use a closure-scoped variable to hold a reference to the actual instance, as for example described here (there are many questions about that).
Possible quick-fixes:
$('body').keydown( (function(){...}).bind(this))
var that=this; $('body').keydown(function(){ that.pressedKeys...; });

"this" keyword inside javascript callback function

I have a decent understanding of the "this" keyword, but for some reason it's still tripping me up in this specific case. Inside the bindEvents method, when I'm binding the submit event to the form, it then executes fetchTweets. I understand that now that it's inside a callback function from the "on" method, so "this" now refers to the form that the event was bound to, rather than the parent object "Tweets".
My understanding was that it is common practice to declare self = this at the top of a method to cache the parent object to prevent later issues with a callback, but in this case it won't work because the sole purpose of that method is to be a callback function for the form submission event.
I know about .call and .apply and even $.proxy, I was just wondering if there was a need to use them in this case, or if I'm missing something obvious. I have this code working using $.proxy, I just thought there might be a smarter way of going about it.
var Tweets = {
init: function ( config ) {
var self = this;
self.config = config;
self.url = 'http://search.twitter.com/search.json?callback=?';
self.bindEvents();
},
bindEvents: function() {
var self = this;
// bind events as needed
self.config.form.on('submit', self.fetchTweets );
},
fetchTweets: function(e) {
e.preventDefault();
var self = this;
var term = self.config.form.find('#term').val();
// grab tweets from the server
$.getJSON(self.url, { q: term }, function(data) {
self.displayTweets(data);
});
},
displayTweets: function(data) {
var self = this;
var tweetList = self.config.list;
tweetList.empty();
// append each tweet to the list
$.each(data.results, function(index, tweet){
$('<li></li>').text(tweet.text).appendTo(tweetList);
});
}
};
Tweets.init({
form: $('#getTweets'),
list: $('#tweets')
});
Instead of using self.<member>, try using Tweets.<member>. You can't do var self = this inside the method, because this is already something other than Tweets. But since you have a variable to refer to the object you're creating, you can just use that. :)
You could also wrap your event handler in an anonymous function as follows:
self.config.form.on('submit', function(e) { self.fetchTweets(e); });
Then don't do var self = this; in any method other than the method that binds the handlers. It's safe to use this.

Categories