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.
Related
I am using the modular design pattern for JS and I keep running into issues when using arguments bound functions. I have a particular function that I would like to bind to different events to keep from having to write the function for each bound event. The only difference in the function, or the argument, is the table that will be updated. The problem is that when I build a function with the arguments I need and pass those arguments to bound events, I get an undefined error, in the console, on load. Keep in mind, I want to stick with this design pattern for the security it offers.
Here is my JS:
var Users = (function(){
var $addRoleForm = $('#addUserRole');
var $rolesTableBody = $('#table-roles tbody');
$addRoleForm.submit(ajaxUpdate(event, $rolesTableBody));
function ajaxUpdate(event, tableName) {
event.preventDefault();
event.stopPropagation();
var url = this.action;
var data = $(this).serialize();
var $this = $(this);
$.ajax({
type: 'POST',
url: url,
dataType: 'json',
data: data,
success: function(data) {
if(data.st === 0){
$messageContainer.html('<p class="alert alert-danger">' + data.msg + '</p>');
setTimeout(function(){
$messageContainer.hide();
}, 7000);
} else {
$messageContainer.html('<p class="alert alert-success">' + data.msg + '</p>');
tableName.fadeOut().html('').html(data.build).fadeIn();
$this.find('input').val('');
setTimeout(function(){
$messageContainer.hide();
}, 7000);
}
},
error: function(xhr, status, error){
console.log(xhr.responseText);
}
});
}
})();
Here is the error I get in the console, on load:
Uncaught TypeError: Cannot read property 'preventDefault' of undefined
I have tried to bind the event like this: $addRoleForm.on('submit', ajaxUpdate(event, $rolesTableBody)); and receive the same results.
Any ideas how to fix this?
You're seeing that issue, because the way you have it written now, ajaxUpdateexecutes, returns undefined and THEN passes undefined to the event listener, so you're basically doing this: $addRoleForm.submit(undefined).
2 Choices here:
1) You can wrap it in an anonymous function:
$addRoleForm.submit(function(event) {
//pass the value of "this" along using call
ajaxUpdate.call(this, event, someValue);
});
$someOtherForm.submit(function(event) {
//pass the value of "this" along using call
ajaxUpdate.call(this, event, someOtherValue);
});
2) You can set the first argument in-advance using bind:
$addRoleForm.submit(ajaxUpdate.bind($addRoleForm, someValue));
$someOtherForm.submit(ajaxUpdate.bind($someOtherForm, someOtherValue));
Using this way, you're binding the value of this to be $addRoleForm, setting the first argument to always be someValue, so it's the same as:
ajaxUpdate(someValue, event) {
//value of "this" will be $addRoleForm;
}
To pass the event, and the custom argument, you should be using an anonymous function call
$addRoleForm.submit(function(event) {
ajaxUpdate(event, $rolesTableBody));
});
This is by far the easiest and most readable way to do this.
What you're doing right now equates to this
var $addRoleForm = $('#addUserRole');
var $rolesTableBody = $('#table-roles tbody');
var resultFromCallingFunction = ajaxUpdate(event, $rolesTableBody); // undefined
$addRoleForm.submit(resultFromCallingFunction);
Where you're calling the ajaxUpdate function, as that's what the parentheses do, and pass the returned result back to the submit callback, which in your case is undefined, the default value a function returns when nothing else is specified.
You could reference the function, like this
$addRoleForm.submit(ajaxUpdate);
but then you can't pass the second argument
The question refers to the Revealing Module pattern. Benefit of using this design is readability. Going with the anon function may work, but defeats the overall purpose of the module pattern itself.
A good way to structure your module to help maintain your scope is to setup helper functions first, then call a return at the end.
Example use case with events:
var User = function() {
// local VARS available to User
var addRoleForm = document.querySelector('#addUserRole');
var rolesTableBody = document.querySelector('#table-roles tbody');
// Helper function 1
function ajaxUpdate(tableName) {
...
}
// Helper function 2
function someFunc() {
...
}
function bindEvents() {
addRoleForm.addEventListener('submit', ajaxUpdate, false);
addRoleForm.addEventListener('click', someFunc, false);
}
function init() {
bindEvents();
}
return {
runMe:init
}
}().runMe();
Helps to "modularize" your workflow. You are also writing your revealing pattern as an IIFE. This can cause debugging headaches in the future. Editing the IIFE to instead invoke via the return is easier to maintain and for other devs to work with and learn initially. Also, it allows you to extend outside of your IFFE into another Module, example:
var Clothes = function() {
function anotherFunc() {
...
}
init() {
User.runMe();
anotherFunc();
}
return {
addClothes: init
}
}().addClothes();
I hope this helps to give you a better understanding of how/when/why to use the JS revealing pattern. Quick note: You can make your modules into IIFE, that's not a problem. You just limit the context of the scope you can work with. Another way of doing things would be to wrap the var User and var Clothes into a main module, and then make that an IIFE. This helps in preventing polluting your global namespace.
Example with what I wrote above:
// MAIN APPLICATION
var GettinDressed = (function() {
// MODULE ONE
///////////////////////////
Var User = function() {
// local VARS available to User
var addRoleForm = document.querySelector('#addUserRole');
var rolesTableBody = document.querySelector('#table-roles tbody');
// Helper function 1
function ajaxUpdate(tableName) {
...
}
// Helper function 2
function someFunc() {
...
}
function bindEvents() {
addRoleForm.addEventListener('submit', ajaxUpdate, false);
addRoleForm.addEventListener('click', someFunc, false);
}
function init() {
bindEvents();
}
return {
runMe:init,
style: someFunc
}
}();
// MODULE TWO
//////////////////////////
var Clothes = function() {
function anotherFunc() {
...
}
init() {
User.style();
anotherFunc();
}
return {
dressUp: init
}
}();
// Define order of instantiation
User.runMe();
Clothes.dressUp();
}());
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 a javascript code
Html5Template_300x250 = function(config) {
this.config = config;
var self = this;
adkit.onReady(this.init());
};
Html5Template_300x250.prototype = {
// Function That Creates Element Var
d: function(id) {
return document.getElementById(id);
},
// Initialize DCO HTML5 template
init: function() {
adkit.onReady(this.handleSVData);
},
handleSVData: function() {
var myData = adkit.getSVData("varName");
alert(myData);
this.startAd();
},
startAd: function(data) {
alert("test2");
}
}
In the above code i have used an external javascript adkit.js and using that method in my code. The initial method is started as
adkit.onReady(this.init());
It is calling a init function and which is then calling other methods including handleSVData which is getting a value from the json file which is in the root folder as
var myData = adkit.getSVData("varName");
The part of the code is working fine but after that line i am calling another method
this.startAd();
But this method is not working and i am getting error
TypeError: this.startAd is not a function
I am not good in javascript and giving me headaches can someone explain me why it is so complicated and what i am doing wrong here ??
When handleSVData is called by adkit it is called in the scope that is not an instance of Html5Template_300x250 - that is why this does not have startAd method.
As for adkit.onReady(this.init()); line.
adkit.onReady expects a function as a parameter. It stores this function variable and calls when it is time for onReady event. This is set correctly by adkit.onReady(this.handleSVData); line. this.init(), however, is a call to init function and your line adkit.onReady(this.init()); passes to adkit.onReady whatever init returns. But it does not return anything - you are passing undefined as parameter.
init: function() {
var template = this;
adkit.onReady(function(){
template.handleSVData();
});
},
And change line
adkit.onReady(this.init());
to
this.init();
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)
Why can't we directly use this instead of self in the following example?
function SeatReservation(name, initialMeal) {
var self = this;
self.name = name;
self.meal = ko.observable(initialMeal);
}
After responses, I've learned:
Yes, there is no need if there is no context switch in class.
But I will use this approach as "convention" although there is no need.
There's no reason why you can't use this directly there (and I would say it would be better for readability if you did).
However, the var self = this; is often needed in situations like the following (basically, any asynchronous action like event binding, AJAX handlers etc, where the resolution of this is deferred until it equals something else);
function SeatReservation(name, initialMeal) {
var self = this;
self.name = name;
self.meal = ko.observable(initialMeal);
setTimeout(function () {
alert(self.name); // otherwise, this is window; use self to keep a reference to the "SeatReservation" instance.
}, 100);
}
It is usually done in order to keep a reference to this when the context is changing. It is often used in event handlers or callback functions. But as mentioned before, there is no reason to use it in your specific example.
You will find more details in the following article: http://www.alistapart.com/articles/getoutbindingsituations
In your example code there is no reason at all to copy this to a variable.
It's usually used when the code uses a callback method. Inside the callback method this doesn't reference the object, so you use the variable for that.
Based on your example, there is "no" reason for doing this.
There is however, situations where it will help you, although some may frown upon it's usage.
i.e.
$('a.go').click(function(e)
{
e.preventDefault();
if(!$(this).hasClass('busy'))
{
$(this).addClass('busy');
$.ajax(
{
success : function(resp)
{
$(this).removeClass('busy');
},
error : function()
{
$(this).removeClass('busy');
}
});
}
});
In the above, $(this) within the success and error callbacks would not reflect to the link you clicked, as the scope has been lost.
To get around this, you would do var self = $(this) i.e.
$('a.go').click(function(e)
{
e.preventDefault();
if(!$(this).hasClass('busy'))
{
$(this).addClass('busy');
var btn = $(this);
$.ajax(
{
success : function(resp)
{
btn.removeClass('busy');
},
error : function()
{
btn.removeClass('busy');
}
});
}
});