How to callback `this` within ajax function - javascript

So I want to use this from an outer function within an ajax success function. I tried to apply these solutions but somehow I can't bring it to work.
// Submit vote on submit
$('.vote-choice-div').on('click', function(event){
event.preventDefault();
// bind the clicked object
this.submit_vote.bind(this); // so I have to bind the element to use it in the ajax function?
// fire ajax
submit_vote(this.id, this);
});
// AJAX for posting
function submit_vote(vote) {
$.ajax({
url : "submit-vote/",
headers: {'X-CSRFToken': csrftoken},
type : "POST",
data : { vote : vote },
success : function(data) {
if(data.status === 1){
console.log(this) // can't access the initial clicked element
}
Uncaught TypeError: Cannot read properties of undefined (reading 'bind')

You have two problems (and a pointless argument).
submit_vote is a global, not a property of the element. To access it, you don't use this.
bind returns a new function. It doesn't mutate the existing one
submit_vote only accepts one argument
So:
const localSubmitVote = submit_vote.bind(this)
localSubmitVote(this.id);
However… bind is only useful if you are going to store a function so you can pass it around or use it multiple times.
You aren't doing that, you're only calling it once, so use call
submit_vote.call(this, this.id);
However… submit_vote isn't a method. It isn't sensible to design it to use this in the first place. So the better approach here is to redesign it to just use the second argument that you were passing before.
function submit_vote(vote, element) {
$.ajax({
// ...
success: function(data) {
if (data.status === 1) {
console.log(element);
}
}
});
}
and
submit_vote(this.id, this)

Related

Enable binding breaks when observable array updated via Ajax success callback

http://jsfiddle.net/FZ6K6/24/
I have a button (Remove inputs) with enable and css bindings that are returned when an observable array contains more than 2 items.
<button data-bind="click: removeInput, enable: Integers().length >2, css { red: Integers().length >2 }">Remove Input</button>
I also have a function (loadIntegerSorter) that sets the observable array to contain 2 items.
self.loadIntegerSorter = function () {
self.Integers([new integer(0, 0, 0), new integer(0, 0, 0)]);
};
I also have a save function that submits via ajax. Within the success callback, loadIntegerSorter is called.
success: function (result) {
if (result.Status == "success") {
isvm.loadSortedIntegers();
}
}
However, this seems to break the enable binding. The CSS binding behaves as expected with the array items = 2. But the Enable binding does not. I can run loadIntegerSorter outside of the Ajax function successfully so I suppose this is a synchronization problem but I don't know what the solution is.
The fiddle I've linked to doesn't fully demonstrate the problem because it depends on making a genuine Ajax request. But I hope it shows enough to understand.
Elaboration:
This results in the expected behaviour from the enable binding:
self.save = function () {
self.isloading();
};
But this doesn't:
self.save = function () {
$.ajax("/Home/Index", {
data: ko.toJSON(self.integerSorter),
cache: false,
type: "post",
contentType: "application/json",
context: self,
success: function (result) {
this.isloading();
}
});
};
And nor does this:
self.save = function () {
self.isloading();
$.ajax("/Home/Index", {
data: ko.toJSON(self.integerSorter),
cache: false,
type: "post",
contentType: "application/json",
context: self,
success: function (result) {
}
});
};
Whatever the cause of the problem, it seems to be related to the ajax call.
1)
Inside of your self.save function you're calling
self.isLoading(true);
Which yields
TypeError: 'undefined' is not a function (evaluating
'self.isLoading(true)')
telling you that self.isLoading is not declared anywhere in your code. This will break code execution even before the ajax request is sent.
2)
Same as 1) but this time for self.msgbox.status(). Undeclared: will break your code.
3)
The function self.loadIntegerSorter appears as self.loadSortedIntegers in the success function. Also, the self.save function appears declared two times. The second one will ovverride the first, but I guess the first one is there just in the fiddle.
4)
Inside of the success function, result.Status doesn't have any sense. You must understand that result is just a string of plain text, accessing the Status property of a string will result in an error. Perhaps you expect the response to be a JSON object with a Status property? If that is the case, you have to deserialize the string either by yourself (JSON.parse(response)) or by telling jQuery to do that for you (replace $.ajax with $.getJSON).
However, it may also be that you're not receiving any JSON back and you just wanted to access the response status, assuming you could do it that way. You can't. Being inside of a success function, you already know that your request has been successfully sent and a response received. No need to check it again.
5)
You're calling the loadSortedIntegers() method on the variable isvm. That's a totally wrong approach, even if it should work now it may cause huge troubles in the future. isvm is a global variable you use to contain an instance of your viewModel. The success function is contained in the viewModel itself, you should access it's own methods with this or self. A class should not access an instance of itself with a global variable. Question: how can I make this and/or self available in the success function? this can be reached by setting the context property to your $.ajax object. Exactly as you write success: function(){} you should write, just before that, context: this or, in your case, context: self.
Do that, and then just change the success function contents with this.loadSortedIntegers().
I've took the liberty to make some edits to your fiddle. Take your time to examine the difference here and to run it here.
Try to use valueHasMutated to push update for observable directly:
self.loadIntegerSorter = function () {
self.Integers([new integer(0, 0, 0), new integer(0, 0, 0)]);
self.Integers.valueHasMutated();
};

How to save with backbone.js without specifying which attributes but with a callback

I'd like to save an altered model to the database (set before). If the save succeeded redirect to another page (as example, could be any other action).
Model.save can have two optional properties. First is a hash of properties, and the second are options (like the success and error callback). http://backbonejs.org/#Model-save
somemodel.set({foo: 'bar'});
//lots of other logic and misc steps the user has to do
somemodel.save(); //on success should go here
Since the attributes are already set, I only need the callback.
In the past I did:
somemodel.save(somemodel.toJSON(), {
success: function() {
//other stuff
}
);
or passing the values again to the save method
somemodel.save(
{ foo: this.$('input').val()},
{ success: function(){}
);
I'm looking for a way to clean this up. The documents state, the model will fire a change state if there are new properties. But I'd want to redirect the user anyway (saving on new content or old/unaltered).
this does not exist:
somemodel.on('success', function(){});
and this, is only for validation:
if(somemodel.save()) { //action }
also "sync" is the wrong event (since it also works for destroy)
Any help?
somemodel.save(
{}, // or null
{
success: function(){}
}
);
will let you save a model with a specific callback without modifying existing keys.
And a Fiddle http://jsfiddle.net/h5ncaayu/
To avoid passing the success callback as an option, you can
use the promise returned by save :
somemodel.save().then(...youcallback...)
or use an event :
somemodel.on('sync', ...youcallback...);
somemodel.save();
Backbone.Model has a very convenient method called "changedAttributes" that will return a hash of changed attributes that you can pass to save. So...
model.save(
model.changedAttributes(),
{
success : _.bind(function() {...},this), //_.bind() will give scope to current "this"
error : _.bind(function() {...},this);
}
);
Nice and neat...

How to pass an element to jQuery post inside a loop

I have the following code:
for (_field in _fields) {
$.post(
'/api/fields',
{ id: _field.id },
function(data, _field) {
alert(data);
} (data, _fields[_field)
);
}
I have to pass the _fields[_field] element to the function that returns the data from the jQuery because loses the reference to the right object during the loop. The problem is that in defining that the post function should have a _field parameter, you also have to specify a parameter for data, or data will be overwritten with _field.
Currently data returns as undefined because I have no data object defined inside the loop. I also tried passing in null, but that also just returns null. I'm looking for a way to pass the element without overwriting the data returned from the post function.
Is there any way to fix this, or is there perhaps an alternative jQuery method that can do what's needed?
You want a function factory function — a function that creates a function:
for (_fieldName in _fields) {
$.post('/api/fields',
{
// Unrelated, but I think this bit is wrong; shouldn't it be
// `id: _fields[_fieldName].id` ? You're trying to use `.id` on
// a string -- see below for a full update
id: _fieldName.id
},
makeHandler(_fields[_fieldName])
);
}
function makeHandler(field) {
return function(data) {
// Use `data` and `field` here
};
}
Note that in the object initializer we're passing into $.post, we're calling makeHandler to it runs and returns the function we'll then pass into $.post. That function is then called when the $.post completes, and has access to the data argument that $.post gives it as well as the field argument to makeHandler, because it's a closure over the context of the call to makeHandler, which includes the field argument. More: Closures are not complicated
Note that in the code above, I changed your variable _field to _fieldName to be more clear: The variable in for..in loops is a string, the name of a property. See also the comment, I think you were trying to use .id in the wrong place. Here's what I think you really wanted:
for (_fieldName in _fields) {
_field = _fields[_fieldName];
$.post('/api/fields',
{
id: _field.id
},
makeHandler(_field)
);
}
function makeHandler(field) {
return function(data) {
// Use `data` and `field` here
};
}
Also note that if _fields is an array, you shouldn't use for..in on it without safeguards. More: Myths and realities of for..in

JS Prototype scope with this/bind

I have the following code that creates an object in JavaScript. It uses prototype to define functions and constructors.
function objectClass(){
this.variables = new Array();
}
objectClass.prototype =
{
contructor: objectClass,
setInfo: function(){
$.ajax({
url: "info.json",
success: function(){
//for each json element returned...
this.variables.push(json[i]);
}
});
}
getInfo: function(){
return this.variables;
},
}
This is a similar example of what I am trying to do. I need to be able to return the array of variables when I call obj.getInfo(). It always throws an error. I believe it is because the "this" is referring to the scope of the ajax success function.
Any ideas on how to get it to reference the objects variable?
That's correct, the this value is not automatically passed and thus not set to the instance. To force this, you can use the context property that $.ajax accepts:
$.ajax({
context: this, // `this` is the instance here
This sets the this value inside the success callback to the one you specified.

Scoping inside Javascript anonymous functions

I am trying to make a function return data from an ajax call that I can then use. The issue is the function itself is called by many objects, e.g.:
function ajax_submit (obj)
{
var id = $(obj).attr('id');
var message = escape ($("#"+id+" .s_post").val ());
var submit_string = "action=post_message&message="+message;
$.ajax({
type: "POST",
url: document.location,
data: submit_string,
success: function(html, obj) {
alert (html);
}
});
return false;
}
Which means that inside the anonymous 'success' function I have no way of knowing what the calling obj (or id) actually are. The only way I can think of doing it is to attach id to document but that just seems a bit too crude. Is there another way of doing this?
You can use variables from the enclosing scope, a technique called "closure". So:
function ajax_submit (obj)
{
var id = $(obj).attr('id');
var message = escape ($("#"+id+" .s_post").val ());
var submit_string = "action=post_message&message="+message;
$.ajax({
type: "POST",
url: document.location,
data: submit_string,
success: function(html) {
alert(obj.id); // This is the obj argument to ajax_submit().
alert(html);
}
});
return false;
}
If you are attempting to load html onto the page via ajax you may want to consider the load() function.
Functions in JavaScript become enclosed in the scope in which they are defined (this is a closure). In this case, a new anonymous success callback function is created every time ajax_submit() is called, so all the variables from the parent scope will always be accessible.
Your code should work just fine as is. If you want to have a callback function, it can be passed as an argument to ajax_submit() and called like this:
…
success: function(html, obj) {
callback(html);
}
…
The variables obj, id and message are all available within the anonymous function.
This is because of a feature in Javascript called closures, which I advise you read up on.
The general gist of a closure is that a function will forever have access to the variables that were present in the scope it was defined in.
The result of this is that you can do:
success: function(html) {
alert (id);
alert (obj);
}
all day long (but note that the obj parameter in the success function will take precedence over the obj variable in your ajax_submit function.)

Categories

Resources