Let's say I have the following code:
var Klass = function(){
var self = this;
this.value = 123;
this.update = function(){
$.ajax({
url: '/whatever',
async: false,
success: function(data){
$.extend(self, data);
}
});
}
}
Lets assume, '/whatever' returns this json object:
{value: 234}
And when I do this:
var obj = new Klass();
obj = ko.mapping.fromJS(obj);
console.log(obj);
We all know obj is now an knockoutjs observable.
And I run this:
obj.update();
console.log(obj);
What I have discovered is, value of obj doesn't get overridden as a simple value 234, but stayed as an observable property.
My questions are:
1) why is this?
2) How do I make the update work as I wanted.
UPDATE: ajax call is not asynchronous.
First issue is that you are extending self, which is a scoped variable and only exists inside the Klass function, not it's instances you create by calling it.
You'll need to call $.extend(this, data); if you need to overwrite value when calling update.
Although I do understand why you are using self there.
But the observable functionality added by calling ko.mapping.fromJS is then lost. value is no longer a function (ko observable) but a scalar value (234). You have to call obj = ko.mapping.fromJS(obj); again to wrap value as observable.
Second issue is that $.get is asynchronous so calling console.log(obj) right after calling obj.update will log the value before the GET response comes. You need to wait for it to execute (use a callback).
Here's a working fiddle.
var Klass = function(){
this.value = 123;
this.update = function(callback){
var self = this;
$.get('/', function(data) {
$.extend(self, {value: 234});
callback.call(undefined);
});
}
}
var obj = new Klass();
obj = ko.mapping.fromJS(obj);
console.log(obj.value());
obj.update(function() {
obj = ko.mapping.fromJS(obj);
console.log(obj.value());
});
Related
I am planning to use the Knockout Mapping plugin to map data returned from ajax calls to the viewmodel under the Durandal framework. However, I did not know how to return the mapped object so that the view can use it. Here's my code for the viewmodel login.js:
define(function (require) {
var system = require('durandal/system'),
app = require('durandal/app'),
ko = require('knockout'),
komapping = require('komapping'),
deferred = $.Deferred(),
loginOptionsUrl = '/json/loginOptions.json',
loginInterfaceData = {};
ko.mapping = komapping;
return {
activate: function(){
var request = $.getJSON(loginOptionsUrl)
.done(function(data){
loginInterfaceData = ko.mapping.fromJS(data);
system.log(loginInterfaceData); //Do a check here; loginInterfaceData contains all the right data
deferred.resolve();
});
return deferred.promise();
},
loginInterfaceData: loginInterfaceData; //However, the view gets an empty object
As you can see, the object returned from ko.mapping is assigned to loginInterfaceData, which I return to the view. However, when I examine the loginInterfaceData object returned by 'viewmodels/login' under the Web Inspector, it is an empty object. The only reasoning I can come up with is that the viewmodel returns loginInterfaceData before the mapping is finished. However, I have no idea how to prevent that from happening.
Any ideas/advice will be most appreciated. Thanks in advance!
This happens because you return a reference to inner loginInterfaceData, but in your activate function you reassign that variable, so previously returned loginInterfaceData will always be an empty object. What you can do is return a getter function for your loginInterfaceData like this:
...
getLoginInterfaceData: function() {
return loginInterfaceData;
}
...
And in your calling code, after activate is done, just call getLoginInterfaceData().
Here is simple example:
function foo() {
var bar = {};
return {
setBar : function(x) {
bar = x;
},
bar: bar,
getBar: function() {
return bar;
}
}
}
var f = foo();
f.setBar(5);
console.log(f.bar); // logs empty object
console.log(f.getBar()); // logs value of inner foo.bar
Apparently the problem lies in returning the loginInterfaceData object before the activate function is invoked, causing the view to receive the original empty object. My solution ends up being to bind loginInterfaceData to empty data first, then updating it once the data has been received via Ajax. Like this:
(...)
ko.mapping = komapping;
loginInterfaceData = ko.mapping.fromJS({}); //instead of assigning loginInterfaceData = {}, I use KO Mapping with an ampty JSON object
(...)
return {
activate: function(){
return $.ajax({
url: loginOptionsUrl,
contentType: 'application/json',
}).done(function(data) {
ko.mapping.fromJS(data, loginInterfaceData);//so here, instead of mapping for the first time, I remap/update it
});
},
loginInterfaceData: loginInterfaceData; //at this point, loginInterfaceData is still empty object but will be updated/remap once ajax call is successful
Hope this helps someone who runs into the same issue as I did. Cheers.
I have written some object oriented Javascript like this:
function MyClass(){
this.SomeFunc(arg1){
result = <some processing on arg1>;
return result;
};
this.SomeOtherFunc(){
return $.ajax({
<some restful call>
}).done(function(){
var localvar = this.SomeFunc(<value obtained by restful call>);
<some operations with localvar>;
});
};
};
var myObj = new MyClass();
myObj.SomeOtherFunc();
And I get an error in the web console: "this.SomeFunc is not a function". If I call it within a function directly, there is no problem. The call fails only inside Ajax. What would be the proper way of making this function call?
this in your callback function is different from the this referring to SomeFunc, try doing:
this.SomeOtherFunc(){
var thatFunc = this; //get hold of this
return $.ajax({
<some restful call>
}).done(function(){
var localvar = thatFunc.SomeFunc(<value obtained by restful call>);
<some operations with localvar>;
});
};
Since you're using jQuery, you can also make sure of the $.proxy(http://api.jquery.com/jQuery.proxy/) method which allows you to pass in the context. For example, you could do
this.SomeOtherFunc(){
return $.ajax({
<some restful call>
}).done($.proxy(function(){
var localvar = thatFunc.SomeFunc(<value obtained by restful call>);
<some operations with localvar>;
}, this)); // Pass in what 'this' should be in method
};
Here, the callback function will execute with this referencing the object passed in as the second parameter.
$.proxy(function(){
// do stuff here
}, this);
Think of the primary function MyClass is your constructor.
This means you have to define the SomeFunc in there, but you are calling it.
That's the problem shown in you console.
You can fix it my defining the function there, instead of calling it:
function MyClass(){
// ----------vvvvvvvvvvv was missing
this.SomeFunc = function(arg1) {
result = <some processing on arg1>;
return result;
};
// ---------------vvvvvvvvvvv same here
this.SomeOtherFunc = function() {
var _this = this
return $.ajax({
<some restful call>
}).done(function(){
// ------------v use _this instead of _this
var localvar = _this.SomeFunc(<value obtained by restful call>);
<some operations with localvar>;
});
};
};
var myObj = new MyClass();
myObj.SomeOtherFunc();
Another way of defining the functions is via the prototype:
MyClass = function() { ... }
MyClass.prototype.SomeFunc = function(arg1) {
return <some processing on arg1>
}
MyClass.prototype.SomeOtherFunc = function() {
var _this = this
return $.ajax({ ... }).done(function(data) {
_this.SomeFunc(data)
})
}
The main difference is, that creating functions in the constructor will create a new function for each call of new MyClass.
Hope that helps.
Im trying to use getters and setters for a project in javascript, I'm getting a json from a jquery get and im setting the value with a setter, I can alert the content in the setter function, so I know that everything is okay, but when I try to return a value with a getter, I got the function instead of the value.
My code is
function _bd() {
this.setJson = function(js) {
json = js;
}
this.getJson = function() {
return json;
}
}
bd = new _bd();
$.get("json.php", function(data) {
bd.setJson(data);
},"json");
alert(bd.getJson);
the last alert returns
function () {
return json;
}
also, using prototype does the same result.
I agree with the previous comments. Invoke the getter-function to retrieve the "json" value.
Probably you want to declare the json variable as well (unless you need it globally of some sorts).
Also, you seem to mixing and matching some object construction patterns. The functional pattern, to simulate "private" vars inside a closure (like json), but you also need the prototypical inheritance, so you can attach the getter/setters to this inside the construtor function. Is it possible to stick to one?
E.g.: trapping privates in closure.
function _bd() {
var json;
var that = {};
that.setJson = function(js) {
json = js;
}
that.getJson = function() {
return json;
}
return that;
}
var bd = _bd();
$.get("json.php", function(data) {
bd.setJson(data);
alert(bd.getJson());
},"json");
E.g. OO style, with constructor function.
function BD(){
this._json = null;
}
BD.prototype = {
getJson: function(){
return this._json;
},
setJson: function(json){
this._json = json;
}
};
var bd = new BD();
$.get("json.php", function(data) {
bd.setJson(data);
alert(bd.getJson());
},"json");
There can be good reasons to use a hybrid style, but it helps if you stick with one approach or the other.
As for "real" getters (IMHO, not worth the insane syntax), try:
function BD(){
this._json = null;
}
Object.defineProperty(BD.prototype,"json",{
get: function(){
return this._json;
},
set: function(json){
this._json = json;
}
});
var bd = new BD();
bd.json = {a: "test"};
console.log(bd);
As stated in the comments :
alert( bd.getJson() );
you are getting the string display of the function because you are not invoking the function
before the response come back, you'll get nothing.. probably undefined.
$.getJson
(
success : function() { alert( bd.getJson() ); }
);
I'm trying to create an object called List. This object has a method add which simply pushes a task object onto this tasks array. I also built a load method to load items from a url.
My issue is I can't seem to reference the add method from within the load method, I get the following error:
Uncaught TypeError: Object # has no method 'add'.
How do I reference the add method from within the load method? The code I am using is below.
function List(){
this.tasks = new Array();
this.add = function(taskItem){
this.tasks.push(taskItem);
};
this.load = function(url){
$.getJSON(
url,
function(data){
$.each(data, function(key,val){
var task = new Task({
id:val.pkTaskId,
title:val.fldName,
status:val.fldStatus
});
this.add(task);
});
}
);
}
}
var userList = new List();
userList.load(url)
Try this:
function List(){
this.tasks = []; // prefer [] over new Array()
this.add = function(taskItem){
this.tasks.push(taskItem);
};
this.load = function(url){
var self = this;
$.getJSON(
url,
function (data){
$.each(data, function(key,val){
var task = new Task({
id:val.pkTaskId,
title:val.fldName,
status:val.fldStatus
});
self.add(task);
});
}
);
}
}
The issue is that this is not what you think it is in the Ajax callback. The callback function is not called in the object's context, it is called in the global context (so this will point to the window object).
Saving an object reference (by convention called self) beforehand is necessary.
this will not always point to the object instance a function "belongs to". In fact, a function does not belong to an object in the same way it does in other languages. this maintains the context a function is called in. Any function can be called in any context:
function A() {
this.val = "foo";
this.say = function () { alert( "A: " + this.val ); };
}
function B() {
this.val = "bar";
this.say = function () { alert( "B: " + this.val ); };
}
function test() { alert( "T: " + this.val ); }
var a = new A(), b = new B();
a.say() // alerts "A: foo"
b.say() // alerts "B: bar"
b.say.call(a); // alerts "B: foo"; (.call() switches the context)
test() // alerts "T: undefined" (val does not exist in window)
test.call(b) // alerts "T: bar" (Ah!)
Unless you define context implicitly (b.say() implies that this will be b) or explicitly (by using call() or apply()), the context will be the global context - which in a browser is the window object. And that's exactly the case for your Ajax callback.
The context for jQuery Ajax callbacks is an object that represents the options used to make the Ajax request. That is, the options object passed to the call to $.ajax(options), merged with $.ajaxSettings. You can override the context by setting the context option. This means calling $.ajax() instead of $.getJSON().
$.ajax({
context: this,
url: url,
dataType: 'json',
success: callback
});
Use this syntax:
function List() {
this.tasks = new Array();
}
List.prototype.add = function(taskItem) {
this.tasks.push(taskItem);
}
var list = new List();
list.add(…);
Also, try to improve your accept rate, people will be more willing to help you.
To build off of Tomalak's answer, you could move the declaration of "self" to the main object level. This has proven to be pretty useful in the case of using this within nested object functions.
function List(){
var self = this;
self.tasks = new Array();
self.add = function(taskItem){
self.tasks.push(taskItem);
};
self.load = function(url){
$.getJSON(
url,
function(data){
$.each(data, function(key,val){
var task = new Task({
id:val.pkTaskId,
title:val.fldName,
status:val.fldStatus
});
self.add(task);
});
});
}
}
var userList = new List();
userList.load(url);
I'm trying to assign some JSON data to a property of a JS.Class instance.
var MyClass = new JS.Class({
initialize: function(uuid) {
this.uuid = uuid;
},
write: function() {
$.getJSON(url+"?callback=?", {}, function(data) {
Assign(data);
});
function Assign(data) { this.content = data; };
}
});
var m = new MyClass("uuid_goes_here");
m.write();
The JSON is received asynchronously, which is why there's a function call within the $.getJSON callback.
The problem I have now is that the this.content within the Assign function is not within the scope of the instance method named write. So whereas this.uuid returns correctly, this.content remains undefined (as you would expect).
Any ideas on how to correct this? I've tried using a global variable as a workaround but the async call doesn't allow for that (plus it's a crappy solution).
Some points to note, in case they matter: I have to use JSONP, so the "?callback=?" has to stay, and I'd like to keep it async.
I would usually go for either czarchaic's version, or replace Accept with a bound method from the object. What you have to bear in mind is that calling Accept() like that (as a function call rather than a method call) will bind this to the global object, i.e. window. I'd try this:
var MyClass = new JS.Class({
initialize: function(uuid) {
this.uuid = uuid;
},
write: function() {
$.getJSON(url+"?callback=?", {}, this.method('setContent'));
},
setContent: function(data) {
this.content = data;
}
});
See http://jsclass.jcoglan.com/binding.html for more info.
You should cache the current instance in the write method and update it after ajax.
write: function() {
var self=this;
$.getJSON(url+"?callback=?", {}, function(data) {
self.data=data;
});
}