Take this example that uses a "widgetManager" object to bind events to all accordions:
widgetManager = {
name : 'widgetManager',
initiate : function(){
$('.accordion').accordion({
onClosing : this.onCloseAccordion.bind(this.name),
})
},
onCloseAccordion : function(){
console.log(name); //I want the event to receive the manager name
console.log(this); //But still be able to know which accordion was clicked
}
}
widgetManager.initiate();
If I bind something to the accordion's onClosing event, it will lose to reference to itself (the accordion that is closing), but I also need a way to be able to pass the 'name' property to the function.
Maybe bind isn't what I'm looking for, but is there a simple way to solve this?
I guess a better wording is, how to pass an object to a function without overwriting the function's scope's this
I'm using Semantic UI's accordions if that helps or changes anything, but the event has no parameters https://semantic-ui.com/modules/accordion.html#/settings
You can simply refer to widgetManager.name to get the name.
widgetManager = {
name : 'widgetManager',
initiate : function(){
var theManager = this;
$('.accordion').accordion({
onClosing : this.onCloseAccordion.bind(this),
})
},
onClosing : function(){
console.log(widgetManager.name); //I want the event to receive the manager name
console.log(this); //But still be able to know which accordion was clicked
}
}
widgetManager.initiate();
If you want something more general, you should be using a constructor function to create different managers.
function widgetManager(name) {
this.name = name;
this.initiate = function() {
$('.accordion').accordion({
onClosing: this.onCloseAccordion.bind(this);
});
return this; // For fluent interface
};
this.onCloseAccordion = function() {
console.log(name);
console.log(this);
};
};
Then you use it like this:
var theWidgetManager = new widgetManager("widgetManager");
theWidgetManager.initiate();
Related
I have a controller that is extended by a a childcontroller. This child needs needs to display an element first and after that raise the overriden parent function. Here is my abstracted code:
Ext.define('ParentController', {
extend: 'Ext.app.Controller',
//lot of code
myFunction(){
//....
this.getSomething();
}
}
and then there is a controller extending this existing one:
Ext.define('ChildController', {
extend: ParentController,
onlyChildFunction(dummy){
//create element
...
element.onClosed = function(){
dummy();
}
return element;
}
//override
myFunction(){
//this.callParent();
var element = onlyChildFunction( this.self.superclass.myFunction() );
element.show();
}
Passing the superclass function doesn't work because the ParentController is using a lot of references to a instance of ParentController (this).
I don't want to copy the code from ParentController directly, because it would mean to maintain the same function twice. One way could be to call myFunction() on element.onClosed again and make some kind of flag there only to do a callParent(). But this sounds ugly - any better suggestions?
Thanks in advance - help is appreciated!
If I understand correctly, onlyChildFunction must be passed a Function. (Here, you are passing the return value of calling the parent class's myFunction.)
Would something like this work ?
Ext.define("ChildController", {
...
myFunction : function () {
var that = this;
var element = onlyChildFunction( function () {
that.self.superclass.myFunction()
});
...
}
}
The idea being that you pass a function that calls the "parent" function in the right context.
In which case, you might be able to use the bind function to the same effect :
var element = onlyChildFunction(this.self.superclass.myFunction.bind(this));
But I'm not sure what the this / self / superclass values are in your case...
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.
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 :)
I'll show you my code first:
function Messages(){
this.postResponseButton = '#postResponseButton';
$(document).ready(this.setEvents);
}
Messages.prototype.setEvents = function(){
$(self.postResponseButton).click(function(){
this.postResponse(); // ERROR HERE
});
}
Messages.prototype.postResponse = function(){
console.log('Post Response');
}
var messages = new Messages();
In the marked line ("ERROR HERE"), it's not recognizing the Messages.postResponse() function when I call it as this.postResponse(). I've also tried self.postResponse() without any success.
I'm sure it's a problem of scope; I'm just not sure how to refer to the actual object. Do I need to set var me = this and use that, or something?
Thanks for your time!
As you have said, the problem is that the context of the click event handler is not the same as the function in which it appears. Either bind (ES5, won't work in old browsers) the function to this:
Messages.prototype.setEvents = function(){
$(self.postResponseButton).click(function(){
this.postResponse();
}.bind(this));
}
Or save a reference to this and use that instead:
Messages.prototype.setEvents = function(){
var that = this;
$(self.postResponseButton).click(function(){
that.postResponse();
});
}
A third alternative would be to use $.proxy, which is effectively an alias for Function.prototype.bind including a fallback for old browsers:
Messages.prototype.setEvents = function(){
$(self.postResponseButton).click($.proxy(function(){
this.postResponse();
}, this));
}
I made a javascript prototype class.
Inside a method I create an jquery click.
But inside this click I want to execute my build function.
When I try to execute a prototype function inside a jquery click it fails because jquery uses this for something else.
I tried some different things, but I couldnt get it working.
Game.prototype.clicks = function(){
$('.flip').click(function(){
if(cardsPlayed.length < 2) //minder dan 2 kaarten gespeeld
{
$(this).find('.card').addClass('flipped');
cardsPlayed.push($(this).find('.card').attr('arrayKey'));
console.log(cardsPlayed[cardsPlayed.length - 1]);
console.log(playingCards[cardsPlayed[cardsPlayed.length - 1]][0]);
if(cardsPlayed.length == 2)// two cards played
{
if(playingCards[cardsPlayed[0]][0] == playingCards[cardsPlayed[1]][0])
{ // same cards played
console.log('zelfde kaarten');
playingCards[cardsPlayed[0]][0] = 0; //hide card one
playingCards[cardsPlayed[1]][0] = 0; //hide card two
//rebuild the playfield
this.build(); //error here
}
else
{
//differend cards
}
}
}
return false;
}).bind(this);
}
The problem is that you're trying to have this reference the clicked .flip element in $(this).find('.card') as well as the Game object in this.build(). this can't have a dual personality, so one of those references needs to change.
The simplest solution, as already suggested by Licson, is to keep a variable pointing to the Game object in the scope of the click handler. Then, just use this inside the handler for the clicked element (as usual in a jQuery handler) and use self for the Game object.
Game.prototype.clicks = function() {
// Keep a reference to the Game in the scope
var self = this;
$('.flip').click(function() {
if(cardsPlayed.length < 2) //minder dan 2 kaarten gespeeld
{
// Use this to refer to the clicked element
$(this).find('.card').addClass('flipped');
// Stuff goes here...
// Use self to refer to the Game object
self.build();
}
}); // Note: no bind, we let jQuery bind this to the clicked element
};
I think you want something like this:
function class(){
var self = this;
this.build = function(){};
$('#element').click(function(){
self.build();
});
};
If I understand correctly, in modern browsers you can simply use bind:
function MyClass() {
this.foo = 'foo';
$('selector').each(function() {
alert(this.foo); //=> 'foo'
}.bind(this));
}
Otherwise just cache this in a variable, typically self and use that where necessary.