JavaScript - Call internal method from event handler - javascript

I apologize if this is a duplicate. I've tried finding a solution for this exact problem but couldn't come up with anything.
Here I have an Object literal:
var Facets = {
searchresults: {},
init: function() {
$('.class').on("click", function(e){
e.preventDefault();
Facets.searchAll();
Facets.getFacets();
});
},
searchAll: function(){
// Some ajax code
this.searchresults = data;
},
getFacets: function(){
$('.somenode').each(function(){
var url = this.makeURL(somedata);
});
},
makeURL: function(){
// creates URL
}
};
Facets.init();
The problem is when the method getFacets calls this.makeURL, I get an error. I'm not entirely sure why because I thought the call to getFacets has the context of the Facets object.
The way I understand it is that when the event handler is triggered, it has the context of the window. Therefore, I call Facets.getFacets and Facets.searchAll so that those functions will know what to call. But I know I'm not understanding something correctly...
I've also tried Facets.getFacets().call(this) to get the correct context, but that didn't work.

You can capture the scope like this:
var Facets = {
searchresults: {},
init: function() {
$('.class').on("click", function(e){
e.preventDefault();
Facets.searchAll();
Facets.getFacets();
});
},
searchAll: function(){
// Some ajax code
this.searchresults = data;
},
getFacets: function(){
var self = this;
$('.somenode').each(function(){
var url = self.makeURL(somedata);
});
},
makeURL: function(){
// creates URL
}
};
Facets.init();

Related

Javascript/jQuery Event Handler Scope inside object using keyword this

I've been trying for days to figure this out. I have read many questions on SO as well as googled it many different ways and read/attempted everything I found. Nothing I have found so far has worked for me and I have rewritten my code a million times it seems trying out different methods for doing this.
I feel like there is some super obvious thing I am missing here, and maybe just need a push in the right direction. If I'm going about this completely wrong and need to restructure everything, I can do that too.
Basically what I am working with is a front end "controller" for lack of a better word, that initializes some variables, sets up some event listeners and responds to user actions.
I don't care if I use jQuery or pure JavaScript, I just want it to work, even if I have to re-write the whole thing. My goal is speed and performance under heavy load. Another option I was considering was node.js but I have limited experience with that, so was hoping to figure it out with jQuery.
When the page loads, I do not get an error, but when I click one of the items that I have set an event listener on, I get the error... TypeError: Cannot Read Property 'apply' of undefined. And it refers to the corresponding line that starts with var scope = this ? function(e)...
The purpose of that line is to have the this keyword refer to the controller object so I can call object methods from within the event handler method. Though it seems it might not be working as I intended.
I tried to just use .on to set up the click and change handlers, but I was having problems with scope there as well. Any help, again, is much appreciated.
(function ($) {
$(function () { //document ready
function Controller(authId, authKey) {
this.user.id = authId;
this.user.key = authKey;
this.init();
};
Controller.prototype = {
eventChange: [ "amt", "multi" ],
eventClick: [ "double", "half", "high", "low" ],
event: { refresh: ['amt', 'multi'], update: ['double', 'half'], process: ['high', 'low'] },
user: { id: '', key: '', name: '', balance: '' },
init: function () {
this.initEvents();
},
initEventz: function() {
for (var i = 0; i < this.eventChange.length; i += 1) {
var ele = document.getElementById(this.eventChange[i]);
var scope = this ? function(e) { this.handleEvent.apply(this, ["change"]); } : this.handleEvent;
if(document.addEventListener) {
ele.addEventListener("change", scope, false);
} else if(document.attachEvent) {
ele.attachEvent("onchange", scope);
}
}
for (var i = 0; i < this.eventClick.length; i += 1) {
var ele = document.getElementById(this.eventClick[i]);
var scope = this ? function(e) { this.handleEvent.apply(this, ["click"]); } : this.handleEvent;
if(document.addEventListener) {
ele.addEventListener("click", scope, false);
} else if(document.attachEvent) {
ele.attachEvent("onclick", scope);
}
}
},
handleEvent: function (e) {
var eventId = e.target.id;
for (var event in this) {
if (this.hasOwnProperty(event)) {
console.log(event);
}
}
}
};
var Controller = new Controller($("#auth").val(), $("#key").val());
}); //end document ready
})(jQuery);
You are losing the reference to this.
You can solve that with this code:
var scope = this ? function(e) { this.handleEvent.apply(this, ["click"]); }.bind(this) : this.handleEvent;
but if you want that the handler have access to the element within his scope with the reference of this you should write this:
var scope = this ? function(e) { this.handleEvent.apply(ele, ["click"]); }.bind(this) : this.handleEvent;
or this
var that = this;
var scope = this ? function(e) { that.handleEvent.apply(ele, ["click"]); } : this.handleEvent;
I have seen other mistake. Because if this is undefined then scope is going to be this.handleEvent but this is going to raise an error because undefined can't have the handleEvent property.

Backbone.js calling function render from event handler

I got my view like this :
render:function(){
this.template = _.template(tpl.get('tplUsersManagement'));
this.$el.html(this.template({models : this.model.models}));
this.$el.i18n();
$('#formAddUser')
.on('invalid', function () {
var invalid_fields = $(this).find('[data-invalid]');
console.log(invalid_fields);
})
.on('valid', this.addUser);
return this;
},
addUser: function(event){
event.preventDefault();
var newUser = new UserModel({
. . .
});
var that=this;
newUser.save({},{
headers:{"X-Token":"theToken"},
statusCode:{
202: function(){
that.render();//here I want to call render function
}
}});
}
}
So I want to call my render function from my addUser function. I try to do it with this=that and then that.render but I got an error and it says that :
Uncaught TypeError: Object # has no method 'render'
I think its because in the event handler this become my form.
You are already aware of the context constraints and that's why you are using the var that = this trick, but there is another place you need to implement it:
var self = this; // I prefer "self" rather than "that"
$('#formAddUser').on('invalid', function () {
// handle invalid data
}).on('valid', function( ev ){
ev.preventDefault();
self.addUser();
return this;
});
The addUser function is part of your view module, but within the valid/invalid event handlers, the context (the this variable) is changed. You'll need to keep a reference of the correct context (var self = this;) before entering the scope of the event handler so that it can be used to call your addUser() function.
There will be 2 options.
1.Use event delegation of backbone view. It will be let you use your view object in event handler.
2.Bind your addUser function to this.
addUser: function() {
...
//do something
...
}.bind(this)

Variable scope in Backbone.js

I have web application in Backbone.js that is using Stripe payment system.
This is part of the model code:
App.Models.Payment = Backbone.Model.extend({
defaults: {
id:null
},
url: function() {
// code
},
initialize:function(){
this.id='';
this.saveClicked= false;
this.saveDetailsChecked= true;
}
this model is used in this view:
App.Views.BillingView = Backbone.View.extend({
el: '#billing-fields',
events: {
'click #billing-footer-buttons .navigation .confirm-btn': 'saveCardClicked'
},
initialize:function(models, options) {
Stripe.setPublishableKey('**********************');
var self = this;
},
saveCardClicked:function(e) {
e.preventDefault();
if (this.model.saveClicked) return false;
this.model.saveClicked = true;
var $form = $('#payment-form');
Stripe.createToken($form, this.stripeResponseHandler);
},
cancelClicked:function() {
vent.trigger('showScreen', 'subscribe-container');
},
stripeResponseHandler:function(status, response) {
var $form = $('#payment-form');
self.saveDetailsChecked = document.getElementById("billing-checkbox").checked;
var atr1 = document.getElementById("billing-card-number").value;
var atr2 = self.model.savedCard;
if(document.getElementById("billing-card-number").value == self.model.savedCard){
self.model.set('use_saved_card','1');
vent.trigger('doPayment');
}else{
if (response.error) {
// code
} else {
// code
}
}
self.saveClicked = false;
}
});
In the saveCardClicked function I am able to access the variables from the model like the saveClicked variable.
But in the stripeResponseHandler I am not able to access the 'saveClicked' variable from the model, in this function this refers to window, and self variable that is defined in the initialize function cannot be accessed also.
stripeResponseHandler is called from the Stripe API.
Is there any way that I can access the savedCard variable in the stripeResponseHandler function or I should use global variable?
Try to use this:-
Stripe.createToken($form, this.stripeResponseHandler.bind(this));
Sure, you can use bind.
Bind works by binding parameters to a certain function call, and will return a function that, when invoked, will be passed the parameter that you declared when calling bind (plus, actually, any other expected parameter that you didn't declared). There is a special parameter to pass to bind. The first one, and it is the this object that will be used when the function is called.
So, what you would do is to call
Stripe.createToken($form, this.stripeResponseHandler.bind(this));
here you are declaring a callback (stripeResponseHandler) that when invoked, will have this as this object (I know the phrase is convoluted, but I think you got it).
Also, keep in mind that your line
var self = this;
is not working as well as it is scoped to the initialize function, thus not visible outside of it.

Self invoking function via setTimeout within object

I would like invoke a method of an js object within the very same object method via setTimeout:
var ads = {
init: function() {
ads.display_ads();
},
display_ads: function() {
console.log('Displaying Ads');
setTimeout('ads.display_ads()', 5000);
}
}
However, I'm getting this error message:
ads is not defined
setTimeout('ads.display_ads()', 2000);
What am I missing here? How would i alter the string within the setTimeout function?
Thanks for your help!
Edit: I use firefox on mac.
Just change it to ads.display_ads, note that this is not a String. i.e.
var ads = {
init: function() {
ads.display_ads();
},
display_ads: function() {
console.log('Displaying Ads');
setTimeout(ads.display_ads, 5000);
}
}
As #FelixKling points out in his comment below, be careful about what this refers to in ads.display_ads. If ads.display_ads is called via ads.init() or ads.display_ads() this will be the ads Object. However, if called via setTimeout this will be window.
If the context is important though, you can pass an anonymous function to setTimeout, which in turn calls ads.display_ads():
setTimeout(function() {
ads.display_ads();
}, 5000);
or
var self = this;
setTimeout(function() {
self.display_ads();
}, 5000);
try this.display_ads,
I'd recommend you to use this for referencing ads
so the code will be like:
var ads = {
init: function() {
this.display_ads();
},
display_ads: function() {
console.log('Displaying Ads');
setTimeout(this.display_ads, 5000);
}
}
So, like jakeclarkson said, ads.display_ads:
setTimeout(hitch(ads, ads.display_ads), 5000);
The difference is that you should use a "hitch" function:
function hitch(scope, callback) {
return function () {
return callback.apply(scope, Array.prototype.slice.call(arguments));
}
}
This function will ensure that the scope of the callback is your ads object. See MDN for a description of the apply function:
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/apply
The answer of jabclab helped me greatly. I was trying to get a game loop to work, but it seems this was referencing window instead of the object I created. Here is a minimal version of the now running code (it just counts up each second and writes it into a div "content"):
function Game(model, renderer){
this.model = model;
this.renderer = renderer;
this.run = function(){
this.model.update();
this.renderer.draw(this.model);
var self = this;
setTimeout(function(){self.run();}, 1000);
};
}
function Model(){
this.data = 0;
this.update = function(){
this.data++;
};
}
function Renderer(){
this.draw = function(model, interpolation){
document.getElementById("content").innerHTML = model.data;
};
}
var game = new Game(new Model(), new Renderer());
game.run();
Instead of setTimeout(this.run, 1000) I used self instead of this to clarify which object is meant (as suggested by jabclab). Thought I'd add this because I'm using an object constructor and has a slightly different syntax. Especially using ads.method didn't work (because Game is not an object yet I guess), so I had to use the last solution.

jQuery binding function on focus

I'm writing a little plugin for jQuery and so far I've got this:
(function($){
$.fn.extend({
someFunction: function() {
return this.each(function() {
var obj = $(this);
obj.focus(someInternalFunction(obj));
});
}
});
function someInternalFunction(obj) {
};
})(jQuery);
The problem is, when i attach someFunction to the object, the object gets focus and binding of someInternalFunction on focus event fails.
Then I tried to bind function to wrap the function call in the other function:
obj.focus(function() {
someInternalFunction($(this));
});
This code works, but it isn't pretty at all. Is it possible to bind function on focus without wrapping it in the other function?
$.fn.bindFocus() = function(){
var internalFunction = function(){
var $this = $(this),
self = this;
// try do stuff here
};
return this.each(function(){
$(this).bind('focus', internalFunction);
});
}
$('#myElement').bindFocus();
Hope it'll help ?
EDT. Sorry, first time get you wrong :)
Here:
obj.focus(someInternalFunction(obj));
^^^^^
... you're calling the function, meaning that its return value is the thing that actually ends up being passed to focus(). Instead you want to pass a function to focus(). Given the fact that you want to pass obj to someInternalFunction, you'll have to define an additional function to wrap it all:
obj.focus(function(){
someInternalFunction(obj);
});
Just to make things clear:
var x = function() { return 3; }; // Defining a function
x; // -> this is a function reference
x(); // -> this is 3

Categories

Resources