Dependent observable called when computed() being evaluated - javascript

I've got computed function which should basically listen to any changes in observable array (Items). The problem is that Knockout calls this function on initialisation. I know it does it to figure out its dependencies but in my case it's a problem as it makes ajax request. Is it any solution beside introducing some counter variable to skip calling ajax on the first call?
Thanks.
function MyViewModel(data) {
var self = this;
self.nameToAdd = ko.observable("");
self.Items = ko.observableArray(data.items || []);
self.add = function () {
self.Items.push({ Name: self.nameToAdd()});
self.nameToAdd("");
};
self.remove = function (item) {
self.Items.remove(item);
};
ko.computed(function () {
$.ajax({
url: "myUrl",
type: 'POST',
data: ko.toJSON(self.Items),
contentType: 'application/json'
});
}, self);
}

I think what you really want is to manually subscribe to your Items array so when it is updated you call the ajax function.
self.Items.subscribe(function(){
$.ajax({
url: "myUrl",
type: 'POST',
data: ko.toJSON(self.Items),
contentType: 'application/json'
});
});
The function passed will only be called when a change has occured to the array itself, thus, not being called on initialization. See fiddle for example.

Related

vue 2.3 AJAX data binding not updating

After a successful ajax call the data rendered on the page is not updating. It is remaining empty / null.
It seems that I am missing how to connect the data returned as a front end variable and the dynamically / live rendered html elements.
Here is the relevant code snippets for context. Is it clear from this what is missing / incorrect?
Javascript
page = new Vue({
el: "#container",
data: {
option_id: null,
option_name: null
},
created:function() {
$.ajax({
type: 'POST',
contentType: 'application/json',
dataType: 'json',
url: 'ajax_methods/get_option',
success: function (ajax_data) {
self = this;
self.option_id = ajax_data.option_id;
self.option_name = ajax_data.option_name;
},
error: function (e) {
console.log(e)
}
})
}
})
HTML
<script type="text/javascript" src="https://unpkg.com/vue#2.3.3"></script>
<div id="container">
<p>{{ option_name }}</p>
<button v-on:click="send_option()"
type="button"
id="left_button"
name="left_button"
v-bind:value="option_id">
</div>
Checking AJAX success
When entering the following in the console, non null values come back as expected:
self.option_id
self.option_name
You need to capture this outside the callback.
created: function(){
const self = this;
$.ajax({
type: 'POST',
contentType: 'application/json',
dataType: 'json',
url: 'ajax_methods/get_option',
success: function (ajax_data) {
self.option_id = ajax_data.option_id;
self.option_name = ajax_data.option_name;
},
error: function (e) {
console.log(e)
}
})
}
first, if that is the exact code, then self I don't think is initialized. Use var self = this or let self = this
But mainly, you need to define self outside of the ajax call. In javascript the this keyword refers to the calling object. directly inside of the created() function, it's the Vue instance. However, this will NOT refer to the Vue instance once inside the ajax callback.
Understand JavaScript’s “this” With Clarity, and Master It
created:function() {
var self = this
$.ajax({
type: 'POST',
contentType: 'application/json',
dataType: 'json',
url: 'ajax_methods/get_option',
success: function (ajax_data) {
self.option_id = ajax_data.option_id;
self.option_name = ajax_data.option_name;
},
error: function (e) {
console.log(e)
}
})
}

Updating a knockout observable after an async ajax call using ko.mapping.fromJS

I have a simple use case. I need to call a WS asynchronously and show the returned JSON on the UI. The JSON that I get back is an object with multiple properties. For simplicity, the code below only has one property. Since I have multiple properties, I am using ko.mapping.fromJS to map the JSON to the object properties. It all seems to work, except the fetched data doesn't update on the UI. If I manually update the observable, it works. But not when using ko.mapping.fromJS.
Javascript
function AppViewModel() {
var self = this;
self.firstName = ko.observable("Bert");
$.ajax({
dataType: 'json',
async: true,
type: 'POST',
url: '/echo/json/',
data: {
json: '{"firstName":"Bob1"}'
}
}).done(function(data) {
console.log(data);
//self.firstName(data.firstName);//This works
self = ko.mapping.fromJS(data); //This doesn't
console.log(self.firstName());
}).fail(function(jqxhr, textStatus, error) {
alert('there was an error');
});
}
// Activates knockout.js
var avm = new AppViewModel();
ko.applyBindings(avm);
HTML
<p>First name: <strong data-bind="text: firstName"></strong></p>
You can run the jsfiddle. You will see that this line works
self.firstName(data.firstName);//This works
and this line doesn't work
self = ko.mapping.fromJS(data); //This doesn't
http://jsfiddle.net/texag93/fakdf5Lw/53/
Two things: 1) You need to create your initial view model with ko.mapping.fromJS, and 2) you need to pass your existing view model as a second parameter to fromJS when updating it.
So something like this instead:
// Activates knockout.js
var avm = ko.mapping.fromJS({firstName: 'Bert'});
ko.applyBindings(avm);
$.ajax({
dataType: 'json',
async: true,
type: 'POST',
url: '/echo/json/',
data: {
json: '{"firstName":"Bob1"}'
}
}).done(function(data) {
console.log(data);
ko.mapping.fromJS(data, avm);
console.log(avm.firstName());
}).fail(function(jqxhr, textStatus, error) {
alert('there was an error');
});
Updated fiddle: http://jsfiddle.net/fakdf5Lw/56/

JQuery AJAX and OOP JS Scope Woes

So, I created an object which makes an AJAX call to populate its properties during the initialization phase. However, I am running into a very weird behaviour: I can print and see the property values fine within the $.ajax() scope, but any public method that returns the value of properties have a return value of "undefined".
Here's what the JS code looks like:
function MyFunction() {
this.myProperty;
this.init();
}
Myfunction.prototype.getPropertyValue = function () {
alert(this.myProperty); // returns 'undefined'
}
Myfunction.prototype.init = function () {
$.ajax({
type: 'get',
url: "getProperty.php",
dataType: "json",
success: function(response) {
this.myProperty = response[0].Property;
alert(this.myProperty) // returns 'Property Name'
}
});
}
My thinking is that within the $.ajax() scope, 'this' is actually referring to something else. So, my question is how do I make sure that 'this.myProperty' is set and doesn't lose its value once we get outside of the AJAX scope?
Any help is much appreciated.
Part of the reason why you're getting "undefined" because of the way you establish the value:
var MyFunction = function () {
this.myProperty;
alert(this.myProperty); // undefined!
this.init();
};
When you declare properties (or variables) without specifying a value, they default to "undefined". Instead:
var MyFunction = function () {
this.myProperty = false;
alert(this.myProperty); // false
this.init();
};
On to the ajax call. You are right that the scope of the callback is not the same as the object. this, in the ajax success function, refers to the jQuery-wrapped XHR object. When you call this.myProperty = response[0].Property, you are actually creating a new property on the ajax object and setting its value. To correct this, you can either use the context option of the jQuery ajax object, OR bind the callback function using the javascript bind method:
success: function(response) {
this.myProperty = response[0].Property;
}.bind(this)
... or:
$.ajax({
type: 'get',
url: "getProperty.php",
dataType: "json",
context: this,
success: function(response) {
this.myProperty = response[0].Property;
}
});
Try it here: http://jsfiddle.net/SnLmu/
Documentation
bind() on MDN - https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
jQuery.ajax() - http://api.jquery.com/jQuery.ajax/
Functions and Function Scope on MDN - https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope
Part of the problem is that the ajax is asynchronous so the properties may not be set when you try to access them (race condition). The other is the value of this inside of the ajax call is not Myfunction. You can fix by:
Myfunction.prototype.init = function () {
var that = this;
$.ajax({
type: 'get',
url: "getProperty.php",
dataType: "json",
success: function(response) {
that.myProperty = response[0].Property;
alert(that.myProperty) // returns 'Property Name'
}
});
}
or you can use the context setting in the ajax call. Per the site:
This object will be made the context of all Ajax-related callbacks. By
default, the context is an object that represents the ajax settings
used in the call ($.ajaxSettings merged with the settings passed to
$.ajax). For example, specifying a DOM element as the context will
make that the context for the complete callback of a request, like so:
Myfunction.prototype.init = function () {
var that = this;
$.ajax({
type: 'get',
url: "getProperty.php",
dataType: "json",
context: Myfunction,
success: function(response) {
this.myProperty = response[0].Property;
alert(this.myProperty) // returns 'Property Name'
}
});
}
var MyFunction = {
myProperty: null,
init: function() {
var self = this;
self.ajax(function(response) {
self.myProperty = response;
self.secondfunction(self.myProperty); //call next step only when ajax is complete
});
},
ajax: function(callback) {
$.ajax({
type: 'get',
url: "getProperty.php",
dataType: "json"
}).done(function(response) {
callback(response[0].Property);
});
},
secondfunction: function(prop) {
alert(prop);
}
}
$(function() {
MyFunction.init();
});

ajax call doesnt return the data from external JS file

I am trying to implement Repository pattern in JavaScript. I have ViewModel which i want to initialize with the data when i call Initialize method on it. Everything seems to be falling in places except that i am not able to return the data from my AJAX call. I can see that data is coming back from the ajax call but when i trying to capture the data in SomeViewModel's done function, it is null.
Can someone please point me out where i am going wrong here?
P.S: Please notice that i am not making Async call so the call chain is properly maintained.
This is how my Repository looks like:
function SomeRepository(){
this.LoadSomeData = function loadData()
{
$.ajax({
type: "POST",
url: "someUrl",
cache: true,
async: false,
contentType: "application/json; charset=utf-8",
data: "{}",
dataType: "json",
//success: handleHtml,
success: function(data) {
alert('data received');
return data;
},
error: ajaxFailed
});
function ajaxFailed(xmlRequest) {
alert(xmlRequest.status + ' \n\r ' +
xmlRequest.statusText + '\n\r' +
xmlRequest.responseText);
}
}
};
This is how my ViewModel looks like:
function SomeViewModel(repository){
var self = this;
var def = $.Deferred();
this.initialize = function () {
var def = $.Deferred();
$.when(repository.LoadSomeData())
.done(function (data) {
def.resolve();
});
return def;
};
}
This is how i am calling from an aspx page:
var viewModel = new SomeViewModel(new SomeRepository());
viewModel.initialize().done(alert('viewmodel initialized'));
alert(viewModel.someProperty);
I have used successfully an auxiliar variable to put the ajax result, when ajax call is inside a function (only works if ajax is async=false) and i need the function does return the ajax result. I don't know if this is the best solution.
function ajaxFunction(){
var result='';
$.ajax({
type: "POST",
url: "someUrl",
cache: true,
async: false,
contentType: "application/json; charset=utf-8",
data: "{}",
dataType: "json",
//success: handleHtml,
success: function(data) {
alert('data received');
result=data;
},
error: ajaxFailed
});
return result;
}
Doesn't matter that it's synchronous (though it really shouldn't be). Returning a value from inside the ajax callback will not cause the value to be returned from the containing function.
Using asynchronous ajax is generally a much better idea anyway, but that will force you to create an API that allows its clients to pass in handlers to be called when the ajax request completes. To do that, you'd give your "LoadSomeData" function a parameter. A caller would pass in a function, and your ajax "success" handler would pass on the results (or some transformation of the results; depends on what it is that you're doing) to that callback. It's the same idea as the callbacks used in the ajax call itself.

javascript undefined

Why cant I access the render function when ajax returns successfully? maybe im going crazy but i've done this before.
Its telling me that this.render is not a function?
DataItem.prototype = {
display: function () {
$('body').append(this.name + ": " + this.getData(this.rootData, this.subData) + "<br />");
},
getData: function (rootData, subData) {
$.ajax({
type: "GET",
url: "json/data.js",
data: "",
dataType: "json",
success: function (json){
this.render(json);
}
});
},
render: function (json) {
var res = [];
for(var i=0, t; t=json.log.entries[i]; i++) {
var p = t.request.url;
if (p!=undefined) res.push(p);
}
return res.length;
}
};
The scope has changed when you try to call this.render(). I believe this contains the ajax request object instead of the DataItem object.
A simple solution is doing like this:
getData: function (rootData, subData) {
var self = this;
$.ajax({
type: "GET",
url: "json/data.js",
data: "",
dataType: "json",
success: function (json){
self.render(json);
}
});
},
Edit: I was wrong, inside the success function the this variable contains the options for the ajax request, however my solution is still correct. See more in the jQuery documentation (http://docs.jquery.com/Ajax/jQuery.ajax#options)
Just to add to #adamse answer. If you want to externalize your success function instead of using an anonymous function you could use the following to pass additional parameters:
function handleSuccess(json) {
this.self.render(json);
}
$(function() {
$.ajax({
type: "GET",
url: "json/data.js",
data: "",
dataType: "json",
// pass an additional parameter to the success callback
self: this,
success: handleSuccess
});
});
since the following code works (i.e "abcd" is printed), I am not sure what is the problem you are facing unless you would share more code.
<script>
DataItem = function(){};
DataItem.prototype = {
display: function () {
return 'a';
},
getData: function () {
document.write(this.render());
return this;
},
render: function () { return "abcd"; }
};
di = new DataItem();
di.getData();
</script>

Categories

Resources