ES6 - How to access `this` element after binding `this` class? - javascript

How can I access this element after binding this class?
For example, without binding this:
$(".button-open").click(function(event) {
console.log(this); // Open
this.openMe();
});
With binding this:
$(".button-open").click(function(event) {
console.log(this); // Polygon {windowHeight: 965, scrollNum: 0}
this.openMe();
}.bind(this));
How can I get and access Open again after binding this?
Full code:
class Polygon {
constructor() {
this.windowHeight = $(window).height();
this.scrollNum = 0;
}
// Simple class instance methods using short-hand method
// declaration
init() {
var clickMe = this.clickMe.bind(this);
return clickMe();
}
clickMe() {
$(".button-open").click(function(event) {
console.log(this);
this.openMe();
}.bind(this));
$(".button-close").click(function(event) {
this.closeMe();
}.bind(this));
}
openMe() {
console.log(this.scrollNum); // 0
this.scrollNum = 200;
console.log(this.scrollNum); // 200
return false;
}
closeMe() {
console.log(this.scrollNum); // 200
return false;
}
}
export { Polygon as default}
Any ideas?
EDIT:
The same issue with jQuery animate:
$(".element").animate({}, 'fast', 'swing', function(event) {
console.log(this); // the element
}.bind(this));
After binding:
$(".element").animate({}, 'fast', 'swing', function(event) {
console.log(this); // undefined
}.bind(this));
Any global or bulletproof way of getting the element again?

1. The best option would be to store the context in a variable and don't overwrite this:
var context = this;
$('.element').on('click', function(event) {
// context would be the this you need
// this is the element you need
});
2. If you're only targeting a single element, you can do the reverse from above and save the element on which you're binding the handler into a variable and then use the variable inside the handler:
var el = $('.element');
el.on('click', function(event) {
// use el here
}.bind(this));
Since you tagged the question with ES6, it might be better to bind the context with an arrow function because using bind is more verbose and also creates an additional function:
var el = $('.element');
el.on('click', (event) => {
// this is the same as in the outer scope
// use el here
});
3. Another option is to use the target property of the event object but this can also be any child within your element (the target is the element that dispatches the event, not the element on which you bounded the handler), thus it might require traversing up the DOM tree to find the element you need, which is less efficient.
var el = $('.element');
el.on('click', ({ target }) => {
while (target.parentNode && !target.classList.contains('element')) {
target = target.parentNode;
}
// here the target should be the element you need
});

There is no generic way to get access to what the value of this would have been if you didn't use .bind(). Javascript doesn't have a way to unbind and get back what this would have been. Instead, you have to look at each individual situation and see if there is some other way to get to the whatever this would have been.
For example, as several of us have said, in a click handler, you can access event.target.
The jQuery animate does not pass any arguments to its callback so if you override this, then there is no generic way to get back to the triggering element. You'd have to go back to the selector again or have saved the value in a containing closure (folks commonly use a variable named self for that).
The only generic way to avoid this issue is to not use .bind() so the value of this is not replaced. You can do something like this:
clickMe() {
var self = this;
$(".button-open").click(function(event) {
// self is our ES6 object
// this is the item that triggered the event
console.log(this);
self.openMe();
});

If you bound your handler, then you can still get the item that was clicked on through event.target within the handler.
https://api.jquery.com/on/
As an alternative you can simply do
const self = this;
or
const me = this;
before any of your declarations of event listeners and without binding any functions. Then within handlers you can both use this to refer to the current element and self or me to refer to the parent scope.

It is already answered, but here is the pattern which I usually use:
If there is single '.element', the below code will work
var el = $('.element');
el.click(function(target, event){
// target is the original this
// this is the scope object
}.bind(this, el[0]));
But if '.element' refers to multiple elements then below code will handle that
var clickHandler = function(target, event){
// target is the original this
// this is the scope object
}.bind(this);
$('.element').click(function(e) {
return clickHandler(this, e);
});

Related

jQuery $(this) not working when inside a function

I have this simple function that copies some html, and places it in another div.
If I put the code for the function in the click event it works fine, but when I move it into a function (to be used in multiple places) it no longer works.
Do you know why this is?
If I console.log($(this)); in the function it returns the window element.
function addHTMLtoComponent () {
var wrapper = $(this).closest(".wrapper");
var component = $(wrapper).find(".component");
var componentCodeHolder = $(wrapper).find('.target');
$(componentCodeHolder).text(component.html())
//console.log($(this));
}
$(".js_show_html").click(function () {
addHTMLtoComponent();
});
codepen here - http://codepen.io/ashconnolly/pen/ebe7a5a45f2c5bbe58734411b03e180e
Should i be referencing $(this) in a different way?
Regarding other answers, i need to put the easiest one:
$(".js_show_html").click(addHTMLtoComponent);
since you called the function manually the function doesn't know the "this" context, therefore it reverted back to use the window object.
$(".js_show_html").click(function () {
addHTMLtoComponent();
});
// Change to this
$(".js_show_html").click(function () {
// the call function allows you to call the function with the specific context
addHTMLtoComponent.call(this);
});
Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
this in the context of the click() event is the element clicked. In the context of the function addHTMLtoComponent this no longer is a reference to the element clicked.
Try passing the clicked object to the function to maintain the object reference.
function addHTMLtoComponent ($obj) {
var $wrapper = $obj.closest(".wrapper");
var $component = $wrapper.find(".component");
var $componentCodeHolder = $wrapper.find('.target');
$componentCodeHolder.text($component.html());
}
$(".js_show_html").click(function () {
addHTMLtoComponent($(this));
});
The special keyword this, when you call a function by itself, is the window object (which is what you observed). For the behavior you need, just add a parameter to the function that loads the appropriate context:
function addHTMLtoComponent(context) {
var wrapper = $(context).closest(".wrapper");
var component = $(wrapper).find(".component");
var componentCodeHolder = $(wrapper).find('.target');
$(componentCodeHolder).text(component.html())
//console.log($(context));
}
$(".js_show_html").click(function() {
addHTMLtoComponent(this);
});
One thing you could consider is that addHTMLtoComponent() could be made into a jQuery function itself:
$.fn.addHTMLtoComponent = function() {
return this.each(function() {
var wrapper = $(this).closest(".wrapper");
var component = $(wrapper).find(".component");
var componentCodeHolder = $(wrapper).find('.target');
componentCodeHolder.text(component.html())
});
}
Now you can call it like any other jQuery method:
$(".js_show_html").click(function () {
$(this).addHTMLtoComponent();
});
The value of this in a jQuery method will be the jQuery object itself, so you don't need to re-wrap it with $(). By convention (and when it makes sense), jQuery methods operate on all elements referred to by the root object, and they return that object for further chained operations. That's what the outer return this.each() construction does.
Inside the .each() callback, you've got a typical jQuery callback situation, with this being set successively to each member of the outer jQuery object.
You have to pass the element as parameter to this function.
eg:
<div onclick="addHTMLtoComponent ($(this))"></div>

JS method binding with 2 different this

I have this small code:
ScenesController.prototype.viewAction = function() {
this.flash = this.di.HelperFlash.hasSupport();
this.$playerElem = !this.flash? $('#html_player') : $('#flash_player');
// the first click is just a sample, I need the same object in the Quailty Change method
$('.scenes_view_video_quailty').on('click', function() { echo($(this));});
$('.scenes_view_video_quailty').on('click', this.viewVideoQuailtyChange.bind(this));
};
ScenesController.prototype.viewVideoQuailtyChange = function(e) {
e.preventDefault();
if (!this.flash) {
echo(this);
echo($(this));
}
};
When I'm clicking the link, I would need pass 2 this variable to the QualityChange method. One with the object (in the bind) and the other is the click event, because I need the clicked element too.
I was trying with the .on('click', {$this: $(this)}, this.method) solution, but dosen't work, the evend.data.$this looks a different object.
I need the same object as I have in the first click method.
(echo = console.log)
Alias the this that refers to the current instance as something else (traditionally, self) and use this to refer to the clicked element
ScenesController.prototype.viewAction = function() {
var self = this;
this.flash = this.di.HelperFlash.hasSupport();
this.$playerElem = !this.flash? $('#html_player') : $('#flash_player');
$('.scenes_view_video_quailty').on('click', function() { echo(self, $(this));});
};
To call a method setting it's this reference you would use Function.apply, for example:
$('.scenes_view_video_quailty').on('click', function(){
self.viewVideoQuailtyChange.apply(self, [$(this)])
});
You can attach any number of variables with bind
You could have a proper method like:
ScenesController.prototype.viewVideoQuailtyChange = function(secondThis) {
}
then use the bind as:
this.viewVideoQuailtyChange.bind(this, $(this));
With this solution you do lose the event though, so it might need some more thought. I'll look into it and update the answer :)

What's the easiest way i can pass an element as a first argument to event handlers in JavaScript?

I know that having the value of this being changed to the element receiving the event in event handling functions is pretty useful. However, I'd like to make my functions always be called in my application context, and not in an element context. This way, I can use them as event handlers and in other ways such as in setTimeout calls.
So, code like this:
window.app = (function () {
var that = {
millerTime: function () {},
changeEl: function (el) {
el = el || this;
// rest of code...
that.millerTime();
}
};
return that;
}());
could just be like this:
window.app = (function () {
return {
millerTime: function () {},
changeEl: function (el) {
// rest of code...
this.millerTime();
}
};
}());
The first way just looks confusing to me. Is there a good easy way to pass the element receiving the event as the first argument (preferably a jQuery-wrapped element) to my event handling function and call within the context of app? Let's say I bind a bunch of event handlers using jQuery. I don't want to have to include anonymous functions all the time:
$('body').on('click', function (event) {
app.changeEl.call(app, $(this), event); // would be nice to get event too
});
I need a single function that will take care of this all for me. At this point I feel like there's no getting around passing an anonymous function, but I just want to see if someone might have a solution.
My attempt at it:
function overrideContext (event, fn) {
if (!(this instanceof HTMLElement) ||
typeof event === 'undefined'
) {
return overrideContext;
}
// at this point we know jQuery called this function // ??
var el = $(this);
fn.call(app, el, event);
}
$('body').on('click', overrideContext(undefined, app.changeEl));
Using Function.prototype.bind (which I am new to), I still can't get the element:
window.app = (function () {
return {
millerTime: function () {},
changeEl: function (el) {
// rest of code...
console.log(this); // app
this.millerTime();
}
};
}());
function overrideContext (evt, fn) {
var el = $(this); // $(Window)
console.log(arguments); // [undefined, app.changeEl, p.Event]
fn.call(app, el, event);
}
$('body').on('click', overrideContext.bind(null, undefined, app.changeEl));
Using $('body').on('click', overrideContext.bind(app.changeEl)); instead, this points to my app.changeEl function and my arguments length is 1 and contains only p.Event. I still can't get the element in either instance.
Defining a function like this should give you what you want:
function wrap(func) {
// Return the function which is passed to `on()`, which does the hard work.
return function () {
// This gets called when the event is fired. Call the handler
// specified, with it's context set to `window.app`, and pass
// the jQuery element (`$(this)`) as it's first parameter.
func.call(window.app, $(this) /*, other parameters (e?)*/);
}
}
You'd then use it like so;
$('body').on('click', wrap(app.changeEl));
For more info, see Function.call()
Additionally, I'd like to recommend against this approach. Well versed JavaScript programmers expect the context to change in timeouts and event handlers. Taking this fundamental away from them is like me dropping you in the Sahara with no compass.

prototype this selector

If I'm using the following function :
clusters.prototype.shop_iqns_selected_class = function() {
if(this.viewport_width < 980) {
$(this.iqns_class).each(function() {
$(this.iqn).on('click', function() {
if($(this).hasClass('selected')) {
$(this).removeClass('selected');
} else {
$(this).addClass('selected');
}
});
});
}
}
To add a property to the clusters function, I know that using this.viewport_width I'm referring to the parent function where I have this.viewport_width defined, but when I'm using the jQuery selector $(this), am I referring to the parent of the $.on() function ?
In JavaScript, this is defined entirely by how a function is called. jQuery's each function calls the iterator function you give it in a way that sets this to each element value, so within that iterator function, this no longer refers to what it referred to in the rest of that code.
This is easily fixed with a variable in the closure's context:
clusters.prototype.shop_iqns_selected_class = function() {
var self = this; // <=== The variable
if(this.viewport_width < 980) {
$(this.iqns_class).each(function() {
// Do this *once*, you don't want to call $() repeatedly
var $elm = $(this);
// v---- using `self` to refer to the instance
$(self.iqn).on('click', function() {
// v---- using $elm
if($elm.hasClass('selected')) {
$elm.removeClass('selected');
} else {
$elm.addClass('selected');
}
});
});
}
}
There I've continued to use this to refer to each DOM element, but you could accept the arguments to the iterator function so there's no ambiguity:
clusters.prototype.shop_iqns_selected_class = function() {
var self = this; // <=== The variable
if(this.viewport_width < 980) {
// Accepting the args -----------v -----v
$(this.iqns_class).each(function(index, elm) {
// Do this *once*, you don't want to call $() repeatedly
var $elm = $(elm);
// v---- using `self` to refer to the instance
$(self.iqn).on('click', function() {
// v---- using $elm
if($elm.hasClass('selected')) {
$elm.removeClass('selected');
} else {
$elm.addClass('selected');
}
});
});
}
}
More reading (posts in my blog about this in JavaScript):
Mythical methods
You must remember this
Don't use this all throughout the code. Methods like $.each give you another reference:
$(".foo").each(function(index, element){
/* 'element' is better to use than 'this'
from here on out. Don't overwrite it. */
});
Additionally, $.on provides the same via the event object:
$(".foo").on("click", function(event) {
/* 'event.target' is better to use than
'this' from here on out. */
});
When your nesting runs deep, there's far too much ambiguity to use this. Of course another method you'll find in active use is to create an alias of that, which is equal to this, directly inside a callback:
$(".foo").on("click", function(){
var that = this;
/* You can now refer to `that` as you nest,
but be sure not to write over that var. */
});
I prefer using the values provided by jQuery in the arguments, or the event object.

How to handle events in jQuery UI widgets

I'm trying to write a jQuery widget following the model given here.
Here is a snapshot of the widget:
(function ($) {
$.widget("ui.notification", {
_create: function () {
if (!this.element.hasClass("ntfn")) {
this.element.addClass("ntfn");
}
this.elTitle = this.element.append("<div class='ntfn-title'>Notifications</div>");
this.elTitle.click(this._titleClick)
},
_titleClick: function () {
console.log(this);
}
});
})(jQuery);
Here the problem is with the scope of "this" inside the _titleClick method, inside the method this points to the title element. But I need it to point to the widget element.
I think one way of doing it will be to use a wrapper class like
var that = this;
this.elTitle.click(function() {
that._titleClick.apply(that, arguments);
});
Is this the best way to solve this problem or is there any general pattern to solve this issue?
Use the this._on() method to bind the handler. This method is provided by the jQuery UI widget factory and will make sure that within the handler function, this always refers to the widget instance.
_create: function () {
...
this._on(this.elTitle, {
click: "_titleClick" // Note: function name must be passed as a string!
});
},
_titleClick: function (event) {
console.log(this); // 'this' is now the widget instance.
},
You should look to jQuery.proxy() http://api.jquery.com/jQuery.proxy/
el.bind('evenname', $.proxy(function () {
this.isMyScope.doSomething();
}, scope));
I wrote a method my own to solve this issue
_wrapCallback : function(callback) {
var scope = this;
return function(eventObject) {
callback.call(scope, this, eventObject);
};
}
In your create, init (or somewhere in your instance) function do this:
_create: function() {
...
// Add events, you will notice a call to $.proxy in here. Without this, when using the 'this'
// property in the callback we will get the object clicked, e.g the tag holding the buttons image
// rather than this widgets class instance, the $.proxy call says, use this objects context for the the 'this'
// pointer in the event. Makes it super easy to call methods on this widget after the call.
$('#some_tag_reference').click($.proxy(this._myevent, this));
...
},
Now define your objects event hander like this:
_myevent: function(event) {
// use the this ptr to access the instance of your widget
this.options.whatever;
},
define var scope=this, and use scope in event handler.
_create: function () {
var scope = this;
$(".btn-toggle", this.element).click(function () {
var panel = $(this).closest(".panel");
$(this).toggleClass("collapsed");
var collapsed = $(this).is(".collapsed");
scope.showBrief(collapsed);
});
},
Another way to do the same thing without using closure, is to pass the widget as a part of the event data like so:
// using click in jQuery version 1.4.3+.
var eventData = { 'widget': this };
// this will attach a data object to the event,
// which is passed as the first param to the callback.
this.elTitle.click(eventData, this._titleClick);
// Then in your click function, you can retrieve it like so:
_titleClick: function (evt) {
// This will still equal the element.
console.log(this);
// But this will be the widget instance.
console.log(evt.data.widget);
};
It used to be via the jquery bind method now on is favoured.
As of jQuery 1.7, the .on() method is the preferred method for
attaching event handlers to a document. For earlier versions, the
.bind() method is used for attaching an event handler directly to
elements. Handlers are attached to the currently selected elements in
the jQuery object, so those elements must exist at the point the call
to .bind() occurs. For more flexible event binding, see the discussion
of event delegation in .on() or .delegate().
_create: function () {
var that = this;
...
elTitle.on("click", function (event) {
event.widget = that; // dynamically assign a ref (not necessary)
that._titleClick(event);
});
},
_titleClick: function (event) {
console.log(this); // 'this' now refers to the widget instance.
console.log(event.widget); // so does event.widget (not necessary)
console.log(event.target); // the original element `elTitle`
},

Categories

Resources