Accessing javascript native "this" inside of jQuery - javascript

How can i access to this.today inside of the Moved function? It will be called via jQuery so the this keyword will be overwritten by jQuery to jQuery object or a DOM element.
Here is something similar to what I have:
(function(Map) {
Map.Timeline = {
today: null,
Init: function () {
jQuery("#timeline").mousemove(Map.Timeline.Moved); // or this.Moved
},
Moved: function (event) {
console.log(this); // jQuery Object or DOM element
console.log(this.today); // fails
console.log(Map.Timeline.today); // works fine
},
// more code here ...

Use jQuery.proxy() to use a custom context in a callback call
jQuery('#timeline').mousemove(jQuery.proxy(Map.Timeline.Moved, this));

You can store this before it is overwritten:
(function(Map) {
var myvar = $(this);
Map.Timeline = {
today: null,
Init: function () {
jQuery("#timeline").mousemove(Map.Timeline.Moved); // or this.Moved
},
Moved: function (event) {
console.log(myvar); // jQuery Object or DOM element
console.log(myvar.today); // fails
console.log(Map.Timeline.today); // works fine
},
// more code here ...

Related

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

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);
});

How to reference widget property from methods called from another context

My jquery-ui widget has some properties that I need to access on a callback. The problem is the context is transient.
Everything I've read says to create my variables in _create constructor and to preserve a reference to the widget in that:
(function ($) {
$.widget("tsp.videoWrapper", {
options: {
value: 0,
playBtnObj: null,
timeboxElement: null,
chapterNavElement: null,
segmentBarElement: null,
positionViewElement : null
},
_create: function () {
var that = this;
var thatElm = $(that.element);
that.Video = thatElm.children("video")[0];
if (that.Video == null) {
console.log("Video element not found.");
return;
}
that._addHandlers();
},
_addHandlers: function () {
this.Video.addEventListener("loadedmetadata", this._videoInited, false);
if (this.Video.readyState >= this.Video.HAVE_METADATA) {
this._videoInited.apply(this.Video); // missed the event
}
},
_videoInited: function (evt) {
console.log(this);
console.log(this.Video.textTracks[0]);
});
}(jQuery));
Trying to reference that in _videoInit creates an error:
Use of an implicitly defined global variable
But the:
console.log(this);
in _videoInit refers to the video itself so calling
console.log(this.Video.textTracks[0]);
fails to because a video doesn't have a Video property. I've omitted a bunch of other code for simplicity but after this call I actually need a reference to the widget to do something with the cues loaded into the video so just doing this:
console.log(this.textTracks[0]);
is not an option.
How do i access the context to get at the video and then do something with it using the properties of the widget instance?
So for instance how do I do this?
_videoInited: function (evt) {
// pretend up in _create I had: that.Cues=[]
that.Cues = that.Video.textTracks[0].cues;
});
I can't use that because of the implicit error as above and I can't use this because this is a video element reference not a videoWrapper widget reference. And i can't do:
that.Cues = that.Video.textTracks[0].cues;
in _create because the cues and other meta data aren't initiated at that point. It seems like such a basic thing to want to do "access an objects properties from it's methods".
Ok so from this preserving-a-reference-to-this-in-javascript-prototype-functions I got the jquery bind method. That question is talking about Prototypes which I thought were like static methods but it seems to work.
Setting up the handler:
var that = this;
$(this.Video).bind("loadedmetadata", function (event) {
event.widget = that; that._videoInited(event);
});
The bind page says to now use the jquery on method
var that = this;
$(this.Video).on("loadedmetadata", function (event) {
event.widget = that; that._videoInited(event);
});
And then using as I wanted:
_videoInited: function (evt) {
console.log(evt); // has a new dynamic widget property
console.log(this); // refers to the widget
Feels a bit weird and loose but seems to work as expected.

Creating reuseable variables within a .on() or any object literal

Sometimes I run into situations where I'm having to create the same variables, and retrieve the exact same type of information over & over again while inside of a object literal, such as an .on() Out of sheer curiosity, and the fact that there has to be a better way, here I am.
NOTE I am not talking about jQuery .data() or any sort of normal window. global variable. I am talking one that is maintained within the closure of the object literal.
Some of these variables change in real-time of course, hence why I always had them inside of each method within .on()
Case in point:
$(document).on({
focusin: function () {
var placeHolder = $(this).attr('data-title'),
status = $(this).attr('data-status');
// etc etc
},
focusout: function () {
var placeHolder = $(this).attr('data-title'),
status = $(this).attr('data-status');
// etc etc
},
mouseenter: function () {
// same variables
},
mouseleave: function () { }
}, '.selector');
Is there a way to just have the variables stored somewhere, and retrieve on each event? They need to be dynamic
$(document).on({
// Basially:
// var placeHolder; etc
// each event gets these values
focusin: function () {
// now here I can simply use them
if (placeHolder === 'blahblah') {}
// etc
}
}, '.selector');
You can use event data to pass some static variables to the event, as well as to make a method-wise trick to pass the "dynamic" ones:
$(document).on({
focusin: function(e) {
var attrs = e.data.getAttributes($(this)),
var1 = e.data.var1;
var placeHolder = attrs.title,
status = attrs.status;
// ...
},
focusout: function(e) {
var attrs = e.data.getAttributes($(this)),
var1 = e.data.var1;
var placeHolder = attrs.title,
status = attrs.status;
// ...
},
// ...
}, ".selector", {
// static variables
var1: "some static value",
// dynamic "variables"
getAttributes: function($this) {
return {
title: $this.data("title"),
status: $this.data("status")
};
}
});
DEMO: http://jsfiddle.net/LHPLJ/
There are a few ways:
Write your own function that will return the JSON above; you can loop through properties to keep from duplicating work.
Write a function that returns those variables (eg: as JSON) so you need only call one function each time.
Write a function to set those variables as global properties and refer to them as needed.
Why not simply add a helper function to extract it?
var getData = function(elm) {
return {
placeHolder : $(elm).attr('data-title'),
status : $(elm).attr('data-status');
};
};
$(document).on({
focusin: function () {
var data = getData (this);
// do stuff with data.status etc.
},
//repeat...
If you're always targeting the same element (i.e. if there's a single element with class selector), and if the values of those variables won't change between the different times the events are triggered, you can store them on the same scope where the handlers are defined:
var placeHolder = $('.selector').attr('data-title'),
status = $('.selector').attr('data-status');
$(document).on({
focusin: function () {
// etc etc
},
focusout: function () {
// etc etc
},
mouseenter: function () {
// same variables
},
mouseleave: function () { }
}, '.selector');
Functions declared on the same scope as those variables will have access to them.
I would recommend wrapping everything in a function scope so that the variables are not global. If these attributes never change, you could do something like:
(function(sel){
var placeHolder = $(sel).attr('data-title'),
status = $(sel).attr('data-status');
$(document).on({
focusin: function () {
// etc etc
},
focusout: function () {
// etc etc
},
mouseenter: function () {
// same variables
},
mouseleave: function () { }
}, sel);
})('.selector');
Otherwise, you could do this (in modern browsers, IE9+)
(function(sel){
var obj = {};
Object.defineProperty(obj, 'placeHolder', {
get:function(){return $(sel).attr('data-title');}
});
Object.defineProperty(obj, 'status', {
get:function(){return $(sel).attr('data-status');}
});
$(document).on({
focusin: function () {
console.log(obj.placeHolder, obj.status);
//etc etc
},
focusout: function () {
// etc etc
},
mouseenter: function () {
// same variables
},
mouseleave: function () { }
}, sel);
})('.selector');
You're doing it close to correct, but the best way to do it is this:
$(document).on({
focusin: function () {
var el = $(this), //cache the jQ object
placeHolder = el.data('title'),
status = el.data('status');
// etc etc
}
}, '.selector');
Data was created for this purpose, don't worry about trying to create re-useable items. If you're delegating the event using object, then it's probably because the elements aren't always on the page in which case you need to get the variables within each individual event.
Finally, don't try to optimize when you don't need to.
You can save them in the window object and make them global.
window.yourVariable = "whatever";
This does what you want but is for sure not the most clean way. If you can, you can consider saving the desired data to the object itself via $(element).data("key", "value")

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