How to reference widget property from methods called from another context - javascript

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.

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

need help understanding closures usage in this code

Here is a simplified snippet from some code I wrote for managing tablet gestures on canvas elements
first a function that accepts an element and a dictionary of callbacks and register the events plus adding other features like 'hold' gestures:
function registerStageGestures(stage, callbacks, recieverArg) {
stage.inhold = false;
stage.timer = null;
var touchduration = 1000;
var reciever = recieverArg || window;
stage.onLongTouch = function(e) {
if (stage.timer) clearTimeout(stage.timer);
stage.inhold = true;
if (callbacks.touchholdstart) callbacks.touchholdstart.call(reciever, e);
};
stage.getContent().addEventListener('touchstart', function(e) {
e.preventDefault();
calcTouchEventData(e);
stage.timer = setTimeout(function() {
stage.onLongTouch(e);
}, touchduration);
if (callbacks.touchstart) callbacks.touchholdstart.call(reciever, e);
});
stage.getContent().addEventListener('touchmove', function(e) {
e.preventDefault();
if (stage.timer) clearTimeout(stage.timer);
if (stage.inhold) {
if (callbacks.touchholdmove) callbacks.touchholdmove.call(reciever, e);
} else {
if (callbacks.touchmove) callbacks.touchmove.call(reciever, e);
}
});
stage.getContent().addEventListener('touchend', function(e) {
e.preventDefault();
if (stage.timer) clearTimeout(stage.timer);
if (stage.inhold) {
if (callbacks.touchholdend) callbacks.touchholdend.call(reciever, e);
} else {
if (callbacks.touchend) callbacks.touchend.call(reciever, e);
}
stage.inhold = false;
});
}
later I call registerStageGestures on a few elements (represented by 'View' objects) in the same page. Something like:
function View() {
var self=this;
..
function InitView() {
...
registerStageGestures(kineticStage, {
touchstart: function(e) {
// do something
},
touchmove: function(e) {
// do something
},
touchendunction(e) {
// do something
},
touchholdstart: function(e) {
// do something
},
touchholdmove: function(e) {
// do something
},
touchholdend: function(e) {
// do something
},
}, self);
Everything works fine, however I'm left wondering about two things in the implementation of registerStageGestures:
First, is it necessary to make inhold, timer and onLongTouch members of the stage ? or will closures make everything works well if they are local vars in registerStageGestures ?
Second, is it necessary to call the callbacks with '.call(receiver,' syntax ? I'm doing this to make sure the callback code will run in the context of the View but I'm not sure if it's needed ?
any input is much appreciated
Thanks!
First, is it necessary to make inhold, timer and onLongTouch members
of the stage ? or will closures make everything works well if they are
local vars in registerStageGestures ?
As far as registerStageGestures() is concerned, var inhold, var timer and function onLongTouch(e) {...}. would suffice. The mechanism by which an inner function has automatic access to its outer function's members is known as "closure". You would only need to set stage.inhold, stage.timer and stage.onLongTouch if some other piece of code needs access to these settings as properties of stage.
Second, is it necessary to call the callbacks with '.call(receiver,'
syntax ? I'm doing this to make sure the callback code will run in the
context of the View but I'm not sure if it's needed ?
Possibly, depending on how those callbacks are written. .call() and .apply() are sometimes used when calling functions that use this internally. In both cases, the first parameter passed defines the object to be interpreted as this. Thus, javascript gives you the means of defining general purpose methods with no a priori assumption about the object to which those methods will apply when called. Similarly, you can call a method of an object in such a way that it acts on another object.
EDIT:
For completeness, please note that even in the absence of this in a function, .apply() can be very useful as it allows multiple parameters to be specified as elements of a single array, eg the ubiquitous jQuery.when.apply(null, arrayOfPromises)...
There are some simple answers, here.
First, closure:
Closure basically says that whatever is defined inside of a function, has access to the rest of that function's contents.
And all of those contents are guaranteed to stay alive (out of the trash), until there are no more objects left, which ere created inside.
A simple test:
var testClosure = function () {
var name = "Bob",
recallName = function () { return name; };
return { getName : recallName };
};
var test = testClosure();
console.log(test.getName()); // Bob
So anything that was created inside can be accessed by any function which was also created inside (or created inside of a function created in a function[, ...], inside).
var closure_2x = function () {
var name = "Bob",
innerScope = function () {
console.log(name);
return function () {
console.log("Still " + name);
}
};
return innerScope;
};
var inner_func = closure_2x();
var even_deeper = inner_func(); // "Bob"
even_deeper(); // "Still Bob"
This applies not only to variables/objects/functions created inside, but also to function arguments passed inside.
The arguments have no access to the inner-workings(unless passed to methods/callbacks), but the inner-workings will remember the arguments.
So as long as your functions are being created in the same scope as your values (or a child-scope), there's access.
.call is trickier.
You know what it does (replaces this inside of the function with the object you pass it)...
...but why and when, in this case are harder.
var Person = function (name, age) {
this.age = age;
this.getAge = function () {
return this.age;
};
};
var bob = new Person("Bob", 32);
This looks pretty normal.
Honestly, this could look a lot like Java or C# with a couple of tweaks.
bob.getAge(); // 32
Works like Java or C#, too.
doSomething.then(bob.getAge);
? Buh ?
We've now passed Bob's method into a function, as a function, all by itself.
var doug = { age : 28 };
doug.getAge = bob.getAge;
Now we've given doug a reference to directly use bobs methid -- not a copy, but a pointer to the actual method.
doug.getAge(); // 28
Well, that's odd.
What about what came out of passing it in as a callback?
var test = bob.getAge;
test(); // undefined
The reason for this, is, as you said, about context...
But the specific reason is because this inside of a function in JS isn't pre-compiled, or stored...
this is worked out on the fly, every time the function is called.
If you call
obj.method();
this === obj;
If you call
a.b.c.d();
this === a.b.c;
If you call
var test = bob.getAge;
test();
...?
this is equal to window.
In "strict mode" this doesn't happen (you get errors really quickly).
test.call(bob); //32
Balance restored!
Mostly...
There are still a few catches.
var outerScope = function () {
console.log(this.age);
var inner = function () {
console.log("Still " + this.age);
};
inner();
};
outerScope.call(bob);
// "32"
// "Still undefined"
This makes sense, when you think about it...
We know that if a function figures out this at the moment it's called -- scope has nothing to do with it...
...and we didn't add inner to an object...
this.inner = inner;
this.inner();
would have worked just fine (but now you just messed with an external object)...
So inner saw this as window.
The solution would either be to use .call, or .apply, or to use function-scoping and/or closure
var person = this,
inner = function () { console.log(person.age); };
The rabbit hole goes deeper, but my phone is dying...

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.

Referencing a parent object in callback functions with jQuery

I've a page that is generated dynamically, and that includes certain number (user-dynamically-defined) of advanced scatter plot charts. I intend to create a JavaScript object which defines the scatter plot itself, i.e. which takes some parameters, some data, and some container ID, and which will create the various elements needed to obtain the visualisation: canvas elements, toolbar, etc.. To do so, I started with the following (simplified) class:
(function () {
if (!this.namespace) { this.namespace = {};}
this._instances = { index: 0 };
this.namespace.ScatterPlot = function (containerId, file, options) {
_instances.index ++;
this.id = this.containerId+"-"+_instances.index ;
this.containerId = containerId ;
_instances [this.id] = this;
// ... Do stuffs with file and options ...
// Initialize elements once the DOM is ready
$(this.updateDOM);
}
namespace.ScatterPlot.prototype = {
updateDOM: function() {
$("<canvas>")
.click(clickCallback)
.appendTo("#"+this.containerId);
//(...)
},
clickCallback: function() {
alert("Some click: "+this.id);
}
}
})();
Each object can be created with:
var v1 = new namespace.ScatterPlot("container1", "foo", "foo");
var v2 = new namespace.ScatterPlot("container2", "foo", "foo");
There are two problems here: (1) in updateDOM, 'this' does not make reference to my initial ScatterPlot object, which means that this example will never work, and (2) similarly, the clickCallback will not be able reference the scatterplot with 'this' either.
I'm new to javascript, and I'm still struggeling to understand the logic of OO programming in javascript, so the question is: I'm I taking the wrong direction here ? After some digging, I could roughly achieve what I wanted by passing this to updateDOM:
$(this.updateDOM(this)); // This blows my eyes but does the trick, at least partially
updateDOM: function(that) {
$("<canvas>")
.click(that.clickCallback)
.appendTo("#"+that.containerId);
//(...)
},
clickCallback: function() {
// Not working either... Should pass 'that' to the function too
alert("Some click: "+this.id);
}
But I don't feel this patters to be very elegant... And the problem is not fixed either regarding the click callback.
Thoughts ?
Have a look at MDN's introduction to the this keyword.
The standard ways of dealing with that issue are using a that variable - not as an argument, but in a separate function:
var that = this;
$(function() {
that.updateDOM();
});
// or
$(this.getClickCallback());
...
namespace.ScatterPlot.prototype.getClickCallback = function() {
var that = this;
return function clickCallback(e) {
alert("Some click: "+that.id);
};
};
Alternatively, you can always use .bind() (or $.proxy for older browsers) which do quite what the second example does in a more generic way:
$(this.clickCallback.bind(this));

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