Enable binding breaks when observable array updated via Ajax success callback - javascript

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

Related

How to callback `this` within ajax function

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)

Value not bound to Ko.observable in knockout.js

This is the code I use to bind to a textbox:
var CategoryViewModel = {
categoryModel: ko.observable({
categoryId: ko.observable(),
categoryName: ko.observable(),
active: ko.observable()
}),
GetCategoryById: function (data, event) {
CategoryViewModel.ClickId(event.target.id);
var ajaxUrl = ApplicationRootUrl("GetCategoryById", "Category") + "/" + CategoryViewModel.ClickId();
$.ajax({
type: "GET",
contentType: "application/json; charset=utf-8",
url: ajaxUrl,
dataType: "json",
success: function (data) {
if (data.isSuccess) {
// This value got bind to textbox
CategoryViewModel.categoryModel(data.data);
}
},
error: function (err) {
}
});
},
CategoryAddButton: function() {
CategoryViewModel.categoryModel();
$('#addCategoryModel').modal('toggle');
}
};
$(document).ready(function () {
ko.applyBindings(CategoryViewModel, document.getElementById("categoryMain"));
});
The CategoryAddButton method is called on button click. I am trying to empty the model value in this method.
Here is the HTML:
<input type="text" name="CategoryName" class="form-control" placeholder="Enter Category Name" data-bind="textinput: categoryModel().categoryName">
The textbox value gets bound on ajax call. However, after the CategoryAddButton method is called, the value does not get bound to the textbox.
First, I'd advise you using a different approach to creating viewmodels from what you've written. Even though some beginner's examples do the same, that's only for simplicity - in reality it is often not a good idea to create viewmodels as object literals. This is because as posted here, here, and here among many other duplicates, accessing another property of the same object can get pretty dirty despite how trivial that task should be.
So, to get over this problem, you should use constructors and the new operator instead, because that enables you to manipulate the object significantly easier. However, I've added this only as a guide to you to write cleaner code, using constructors and new object syntax won't solve the problem alone.
So let's get back to your question. To find out the reason why your code does not work, look how you manipulate your data when the binding works and how when it doesn't.
You said that after the AJAX-call had succeeded the value got updated properly so the binding worked. This is because in the success callback of the AJAX-call, you are actually passing an object into categoryModel. However, I'd point out that what you pass to it is not an observable but just a plain object, whereas you initially create it with its properties being observables! So even there you might run into problems.
You also said that after the button had been clicked, the value was not updated. I am not really sure what you even want to achieve here; what do you want to be displayed and where should the data come from? Because this line of code you've written:
CategoryViewModel.categoryModel();
is simply a getter -- it won't alter the object in any way, you are just reading its value. Without actually modifying it, of course nothing will change.
So, I'll give you a possible way of implementing the whole thing, and I suggest you read more on javascript object constructors and how to use knockout properly using them.
function categoryViewModel() {
var self = this;
// Don't wrap these in another observable, that's totally needless.
this.categoryId = ko.observable(null);
this.categoryName = ko.observable(null);
this.active = ko.observable(true);
// You could actually define this on the prototype
// but I don't want to make it even more complicated
this.GetCategoryById = function(categoryId) {
// You could do this if the value passed can either be a plain value
// or an observable; ko will return the actual value to you.
var catId = ko.utils.unwrapObservable(categoryId);
var ajaxUrl = ApplicationRootUrl("GetCategoryById", "Category") + "/" + catId;
$.ajax({
type: 'GET',
contentType: "application/json; charset=utf-8",
url: ajaxUrl,
dataType: "json",
success: function(data) {
if (data.isSuccess) {
// Correct the object references according to
// the structure of the response if needed.
self.categoryId(data.data.id);
self.categoryName(data.data.name);
self.active(data.data.isActive);
}
},
error: function(err) {
}
});
};
this.CategoryAddButton = function() {
self.categoryId(null);
self.categoryName(null);
self.isActive(true); // If you want to default to true.
$('#addCategoryModel').modal('toggle');
};
};
$(document).ready(function () {
ko.applyBindings(new categoryViewModel(), document.getElementById("categoryMain"));
});
And your HTML could be:
<input type="text" name="CategoryName" class="form-control" data-bind="textInput: categoryName" />
As for the GetCategoryById function, it would even be nicer if you assigned that to the function prototype, rather than assigning a "copy" to each and every object created. But since I assume you'll only ever have 1 instance of your viewmodel, I'll consider that out of scope for now.

Variables in js

I am quite a noob in regards to js and today I come across bit of js I don't really understand and I would like to. Could you please shed some light ? There is Kendo involved but the question is plain js.
I have a nested grid, eg. each row can expand into other grid and each of this grid has its own datasource. I populate the datasources via the method below one by one as user clicks and I had a problem with forcing the datasource to read when it receives async response from the create call. (calling the read is pretty much incorrect thing to do, but Kendo has its own bugs - not a point here.). My problem was, I didn't have an instance to call the read() on, the method only returns datasource and assigns it to a grid, when the event comes back I can't find any reference to anything I could get the correct datasource instance from. this is different context in here.
In order to resolve this I added a datasource variable into the method what builds the datasource and I return the variable instead the datasource, which is the same thing. However this helps to have something to call the problematic read() on. Now in my create handler I call create on the variable I am returning in the method during innit. Well it works, but I am not sure if every datasource is calling read on its own instance after innit ?
function _getDatasource() {
var datasource = new kendo.data.DataSource({
transport: {
read: {
url: serviceBaseUrl + "ReadQuestionnaire",
dataType: "json",
type: "POST",
contentType: "application/json; charset=utf-8",
},
create: {
url: serviceBaseUrl + "CreateQuestionnaire",
dataType: "json",
type: "POST",
contentType: "application/json; charset=utf-8",
complete: function (jqXhr, textStatus) {
if (CheckForExceptions(jqXhr, textStatus) == false) {
// this is the variable I am not sure about
// after innit does this always refers to this same datasource ?
datasource.read();
}
}
}
}
});
return datasource;
}
Your solution is correct, and yes, the datasource.read() call is the correct datasource object in each case.
Here's why this works: closures.
A closure is the ability to have a variable declared in one function accessible from a nested function. Or, for a more accurate description, see the wikipedia page: http://en.wikipedia.org/wiki/Closure_(computer_science)
Here's a very simple example:
function doStuff(){
var name = "Derick";
function sayMyName(){
console.log(name);
}
sayMyName();
}
doStuff();
In this example, I'm declaring a variable name inside of the doStuff function. Then I'm nesting another function inside of the first function. The sayMyName function is accessing the name variable through the use of a closure.
When I call doStuff(), the variable is defined and assigned to a value. Then the sayMyName function is defined and it uses that variable. I then call sayMyName() and it logs the name to the console.
Similarly, in your code you are creating a variable that is assigned to the instance of the DataSource. Later, you are defining a function for the complete callback. After the data source has loaded and the complete callback is fired, you are accessing the same dataSource variable that you had assigned to the DataSource instance through the use of a closure around that variable.
Since you are declaring var dataSource every time you call _getDataSource, you are creating a new variable / reference, assigned to a new DataSource instance. I don't think you need to return datasource at the bottom of your function, though... at least, not for the complete callback function to work. Maybe you need it for something else, outside of this function, though?
For more information on closures in JavaScript:
https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures
How do JavaScript closures work?
http://www.javascriptkit.com/javatutors/closures.shtml
http://www.watchmecode.net/javascript-scope (paid screencast)
HTH

jQuery passing parameters into .ajax()

I'm having issues passing parameters/variables into the ajax() function.
The function below accepts two parameters. One is 'element', the success option of .ajax() uses, that works fine so no need to focus on that. The 'tx' is either a single value like 'menu' or a couple of values separated by a colon like this: "menu:categories:brands"
If there is only one value in 'tx' then only one AJAX request needs to be sent and that works fine. If there is more than one value in 'tx' the function split's it using ":" is the delimiter and then passes offset[0] as the value of 'tx' into the AJAX request and then store's the rest of the values in 'x'.
What I'm having issues with is running this function recursively once the AJAX request is complete for the first value. The var 'more' stores a bool value if there is more values left to process or not. However, when I write in an if argument using 'more == true' into the success or complete option of .ajax() it doesn't reflect the value stored in that variables, it always returns false.
Before some one answers, with "You should structure the function that calls this function to only pass a single value into that parameter," lets suppose that this is an impossible venture that is outside the realm of my control.
I have no idea why this happening. I know it's probably something very simple that I'm overlooking but I've been up for about 16 hours now and this issue has plagued me for at least half that time.
Any help here is appreciated. Here is the function:
function getContent(element, tx) {
e = element
modAmount = tx.split(':')
if (modAmount.length > 1) {
x = ''
tx = modAmount[0]
for (i=1;i<modAmount.length;i++) {
x = x + modAmount[i]
if (i != (modAmount.length)-1){
x = x+":"
}
}
more = true
}
else {
more = false
tx = modAmount[0]
}
$.ajax({
type: "POST",
url: "getModule.php",
data: "modName="+tx+"&DB=<?php echo DB ?>",
success: function(data){
if ($( element ).find('p').text() == "No Content"){
$( element ).find('p').remove();
$( element ).html("<div onclick='destroy(this)' id='destroy' class='ui-corner-all destroy' title='Remove Module'>"+data+"</div>")
}
else {
$( element ).append("<div onclick='destroy(this)' id='destroy' class='ui-corner-all destroy' title='Remove Module'>"+data+"</div>");
}
},
complete: function() {
if (more == true) {alert(x)} // always returns false
}
});
}
There's already one glaring error here: you're not declaring your local variables using the var keyword. This means that there's only one instance of any of those variables, in the global context.
I'm not sure if this is your problem, but it certainly seems like it could be. Consider the case where you call getContent "foo:bar". The first call sets more to true, but then the second recursive call sets more back to false. When the first XmlHttpRequest finishes, the completion callback will see the value of false rather than the true you were expecting, since those two calls are sharing the same variable for more.
You should read up on how JavaScript/ECMAScript scoping works, and gain a solid understanding of how closures work in the language. This will prevent a lot of head-scratching in the future. Google around for writings by Douglas Crockford; that's a good starting point.
tl;dr: define your local variables using the var keyword.
Because "more" isn't scoped within your callbacks.
I recommend using the context property you can send to the ajax call to set the "this" variable in success/complete callbacks:
http://api.jquery.com/jQuery.ajax/

Accessing a variable whose value is set in an AJAX function

I've got a problem. I've been trying to solve the problem of how to avoid using global variables. I decided to use an object with functions on it instead, and wrote a script accordingly. However, the script won't run as expected:
GetXML: function() {
$.ajax({
type: "GET",
url: "questions.xml",
dataType: "xml",
success: function(xml) {
this.xml=xml;
$(window).trigger("canUseXML");
var docLength = $(xml).find('Question').length + 1;
alert("this really is an '" + this.xml + "' and the actual doclength is: " + docLength)//This is the first alert I mention.
} //close success
});//close AJAX
alert("yes! this is still a: " +this.xml) //This is the second alert I mention.
},
If it all ran as expected, both alerts would result in an 'object XMLDocument' (a variable declared elsewhere in this line: this.xml = null;). The first runs as expected. However, the second, outside of that function, returns value "null". Why?
Thanks, Elliot Bonneville
P.S. I've been stuck on the seemingly simple question of passing variables between functions for a week.
In JavaScript "this" always refers to the “owner” of the function executed, or rather, to the object that a function is a method of. So your first and second "this" does not refer to the same thing.
Assuming that the "success" function executes before the second alert (which it probably doesn't). You could do something like this:
GetXML: function() {
var that=this;
.
.
and then use "that" instead of "this".
Scoping.
Once you travel outside of anonymous function(xml) this.xml no longer exists.
You have two problems:
Scoping: this inside the callback is not your object with GetXML as a member. Use something like me = this; in the constructor of your object and then use me instead of this to explicitely set the value.
You callback function waits for a success of your Ajax request, but the rest of the script is going on, which is the purpose of callbacks. So, the Ajax request is made, and then you do alert("yes! this is still a: " +this.xml), while the callback has not yet been executed.
This is probably happening because ajax takes time to process. Javascript doesn't wait for one request before executing the next command, so while AJAX is being called, its trying to figure out what happened to this.xml.

Categories

Resources