Ajax response is not changing my viewmodel - javascript

I bind a value and a list and want to change it from inside an Ajax callback. I retrieve a fresh value in .get(), but when I the callback for .get() actually happens, and I assign the retrieved value to my view model's property, the UI does not refresh. Here is my code:
function SearchViewModel() {
this.count = ko.observable(count);
this.list = ko.observableArray(list);
//I had count and list before I assigned.
this.addPage = function() {
var form = $('#form');
var serializedData = form.serialize();
$.get("{% url 'search:search' %}", serializedData, function(response){
console.log(this.count); // it's undefined here.
this.count = response.count;
console.log(this.count); // it's the value I want to updated, e.g. 20. But UI is not refreshedenter code here
});
};
}
I want to update a list too in the callback, but right now even a simple count value is not being updated. I read about many related solutions on stackoverflow, and tried several, but none of them worked.

Try:
this.count(response.count);
That should do the trick.
For more information about observables check out http://knockoutjs.com/documentation/observables.html
You also have a potential scope issue in your code. When you are reffering to this in the callback you are not guaranteed that you get the scope of the viewModel. Therefore you should add this line outside of the callback:
var self = this;
And inside the callback you should change to:
self.count(response.count);

Related

Function parameter not getting the value directly

Can someone please explain why the function not getting the value of the object in the first way?
I got the Backbone.View:
var some.view = Backbone.View.extend({
elements = {},
//...
//some init, filling up elements...
//...
stopTask: function() {
// Calling the function with parameters explained later
stopThisTask(...);
}
});
And the function:
function stopThisTask(task){
console.log(task);
}
When I call stopThisTask in the following way, the task parameter is undefined
stopThisTask(this.elements);
However, when I do it like this, the task has the value
var tmp = this.elements;
stopThisTask(tmp);
Can someone please explain why is that?
If I know right the variables are passed by value, and the obects are passed by references. However, does that mean in some way I loose the reference for the elements object?
I'm suspecting that the this.elements gets resolved inside the stopThisTask function, so this will point to stopThisTask instead of to the caller of stopThisTask.
By explicitly setting the tmp parameter in the caller, you guarantee the correct this scope is used.
Should be equivalent to
stopThisTask.call(this, this.elements);

Ajax-assigned variable inaccessible after assignment

So, I have a script that fetches data from a SQL database and I'm trying to build a JS wrapper for it. I'm using the following functions to call that script and use information from the DB as soon as it's ready.
var User = function() {
this.email = null;
//Async call to get user values
this.current(function(response) {
this.email = response.email;
//The value is assigned/usable at this point
});
};
User.prototype.current = function(callback) {
$.post("php/db_functions.php", {object: "CurrentUser"}).done(function(result) {
callback(JSON.parse(result)[0]);
});
};.
Everything seems to work fine, but if I try to access the value from the object after I've created it, it returns undefined, like so:
var me = new User();
//And then, way after the async call and when me.email should be defined
me.email //Returns undefined
Why can I use it in the callback, but not afterwards?
In a function, the context variable this points to either the global window object or to undefined in the strict mode, unless specified otherwise by the caller. Therefore, you need to either capture the value of this in a local variable:
//Async call to get user values
var that = this;
this.current(function(response) {
that.email = response.email;
});
or call the function in the desired context using either the call or the apply method:
User.prototype.current = function(callback) {
var that = this;
$.post("php/db_functions.php", {object: "CurrentUser"}).done(function(result) {
callback.call(that, JSON.parse(result)[0]);
});
};.
Also, as others have mentioned, there is no guarantee the AJAX request will have finished by the time the User contructor returns.
This is a timing bug since the variable is not assigned until the async call returns. You can't access email right away.

Force KO to update automatically

So I have two viewModels, one has a document style database in an observable:
var databaseViewModel = function () {
var self = this;
self.database = ko.observableArray([]).publishesTo("database");
}
var calcViewModel = function () {
var self = this;
self.replicatedDatabase = ko.observableArray([]).subscribeTo("database");
}
These get applied thusly:
ko.applyBindings({
databaseViewModel: new databaseViewModel(),
calcViewModel: new calcViewModel()
});
The only problem is, that the drop down box tied to replicatedDatabase doesn't show any items. I know I can force a binding update:
database.valueHasMutated();
But I don't know where and when.
I have tried after the ko.applyBindings however, I'm not sure how to access the newly created databaseViewModel. I've tried inside databaseViewModel after it has been created, but I believe that KO automatically updates the bindings when they've been binded, so not sure this actually makes a difference, it didnt on the dropdowns anyways.
I'm not really sure what I should be doing here.
For reference, I'm using knockout-postbox to do message bus style communications, subscribeTo and publishesTo. So as soon as the database observable is changed it will notify all subscribers, so I thought that maybe replicatedDatabase would have been update in the instance that databaseViewModel was initiated.
So, rather than force knockout to update the values I chose a different approach.
Realistically speaking the page would initially be populated with some data from a server, so with this in mind I proceeded by making a global variable holding the initial data:
var serverData = [{}];
Then just simply populate the observableArray's using Ryan Niemeyer mapping function:
ko.observableArray.fn.map = function ( data, Constructor) {
var mapped = ko.utils.arrayMap(data, function (item) {
return new Constructor(item);
});
this(mapped);
return this;
};
This way both viewModel's start off with the initial data, and when the database viewModel gets updated this permeates through to the other viewModel's

Maintaining the jquery object in ajax post

I want to maintain the datatable object in ajax call. Please refer below code
$(function(){
function applyDataTables(options) {
//datatable object
var $datatable = $("#table1").dataTable(options);
if (some condition) {
this.dataTableObj = [];
this.dataTableObj.push($datatable);
} else {
$datatable = dataTableObj[0];
}
...............................................
}
})();
first time page load, it will call this function and find some datatable object after that am making some ajax post that time also it will trigger the same function and finding the datatable object.
so, i want to maintain the $datatable object when the page loaded first time, during some ajax posts i want to use this same object for other purpose how can i maintain the $datatable object in ajax post.
if i add that object to "this.dataTableObj" i can able to get the value of old object in ajax post.whether it is correct way of maintaining existing object in javascript.
Thanks,
Well "this" always refers to calling object.
but if you call
applyDataTables(options) //this will be window object;
and it can be overridden using call, apply or bind method
applyDataTables.call(someObj,options) //this will point to someObj
In your function this can be confusing.
Plus if you are passing it to some callback this can be overriden by call or apply method.
So instead of storing datatableObj in this you can store it on some global namespacing whose scope will be on all ajax call.
You may define
var globV={
dataTableObj:[]
};
//you can use different namespacing too instead of globV
$(function(){
function applyDataTables(options) {
//datatable object
var $datatable = $("#table1").dataTable(options);
if (some condition) {
globV.dataTableObj = [];
globV.dataTableObj.push($datatable);
} else {
$datatable = globV.dataTableObj[0];
}
...............................................
}
})();

Why isn't my data class member directly available?

I've been using jQuery for a couple of years now with very limited understanding of vanilla javascript. Scope, the object model, and many of the design patterns that I see used in javascript baffle me. I'm trying to implement a class that will eventually be used in a scheduling plugin that I need to write and I'm having a hard time understanding why data stored in one of my class members doesn't seem to be available. I'm not sure if the issue is with scope or some other behavior that I don't understand.
I have the following code with 2 questions in the comments at the appropriate places. The first question is whether or not my scope workaround in my getJSON call is the correct way of handling the scope issue inside getJSON. My second question is why I can't directly access schedule.data.
function Schedule() {
this.year = null;
this.month = null;
this.day = null;
this.start_datetime = null;
this.start_timestamp = null;
this.end_datetime = null;
this.end_timestamp = null;
this.data = [];
return this;
}
Schedule.prototype.init = function() {
var url = '/tripsys/new_admin/employee_schedule/get_employee_schedule_data/' + this.start_timestamp + '/' + this.end_timestamp;
var self = this; // 1. trying to work around scope issues. Is this the correct way to handle the scope problems here?
$.getJSON(url, function(data) {
self.data = data;
});
}
var schedule = new Schedule();
$(document).ready(function() {
schedule.year = $('#year').text();
schedule.month = $('#month').text();
schedule.day = $('#day').text();
schedule.start_datetime = new Date(schedule.year, schedule.month - 1, schedule.day);
schedule.start_timestamp = Math.round(schedule.start_datetime.getTime() / 1000);
schedule.end_datetime = new Date(schedule.year, schedule.month - 1, schedule.day, 23, 59, 59);
schedule.end_timestamp = Math.round(schedule.end_datetime.getTime() / 1000);
schedule.init();
console.log(schedule); // if I log the whole schedule object the data that I expect to be in the "data" member is there
console.log(schedule.data); // 2. why is the data that I expect to be in the "data" member not there when I access schedule.data directly?
});
Thanks for your insight.
Well point number one is correct in that you need to save the this reference while you still can because when the inner function is called by jQuery, this inside the function will refer to the ajax object.
In the second comment you are logging schedule.data before the ajax request has completed. You can see schedule.data when you log schedule because when you log an object in google chrome, the object properties are retrieved after you manually "expand" the object in chrome console. When you manually "expand" it, at that time the request has already completed.
You can reproduce it like this:
var a = {};
console.log(a); //do not "expand" the object properties yet
console.log(a.property); //undefined
a.property = "value";
//"expand" the already logged object and it will have "value"
Yes, that will work, although it isn't a scope issue as much as it uses variable scope to get around a context issue.
To access schedule.data, you need to wait until the data has arrived. In other words, place the console.log code in the callback.
The issue is that the ajax call has not returned before you log the object. If you want to make the ajax call synchronous and the init function gets a result before you log, use the async param on an ajax jQuery call:
$.ajax({
url: url,
dataType: 'json',
async: false,
success: function(data){
self.data = data;
console.log(data);
}
});
This is probably because in this line schedule.init();, it makes an ajax call which has not completed yet when you then do console.log(schedule.data);. Ajax calls are asynchronous. Calling them only starts the networking operation and then they return immediately. They are not completed until the success handler function has been called (and that's when self.data is assigned).
So, if you want to look at the data for the schedule object that was obtained in the .init() function, you have to wait until that ajax call has completed or do something with the data in the completion function.

Categories

Resources