accessing object properties with object method - javascript

I am trying to build an application called myApp which has the property regularNameErrors and the Method populateJSON which uses an AJAX call to get a JSON object which is then added to the property declared as one of the arguments to the method.
The function to get the data definitely works because the alert at position 3 gives the correct list of keys. however the alert at 1 returns undefined and the alert at 2 blank.
When I replace the line destination = data with myApp.regularNameErrors = data again it works so I assume that I have misunderstood how to pass object properties to object methods. However I want to use the method populateJSON several times and need to know the correct way to pass it properties.
var myApp = {
init: function () {
myApp.populateJSON(myApp.regularNameErrors, 'data/spelling.json').done(function () {
alert(myApp.regularNameErrors['kingscross']); //1
});
},
regularNameErrors: {}, //Object of spelling mistake:proper key:value pairs
populateJSON: function (destination, source) {
var def = $.Deferred();
$.getJSON(source, function (data) {
destination = data;
alert(Object.keys(myApp.regularNameErrors)); //2
alert(Object.keys(data)); //3
def.resolve();
});
return def.promise();
},
};

With the ability to use the deferred properties of the returned getJSON object the functions can be reduced to a few lines such that it is not necessary to create a new function called populateJSON
var myApp = {
init: function () {
$.getJSON('data/spelling.json').done(function(data) {
myApp.regularNameErrors = data;
});
},
regularNameErrors: {},
};

destination is no "pointer" to the property that you passed into the function, but a variable holding the value which the property evaluated to. When you assign to destination, you just write to the local variable of your populateJSON function.
If you want to be able to declare the destination, you would need to pass a base object (or always use myApp) and a property name:
var myApp = {
init: function () {
myApp.populateJSON('regularNameErrors', 'data/spelling.json', function() {
alert(Object.keys(myApp.regularNameErrors)); //2
alert(myApp.regularNameErrors['kingscross']); //1
});
},
regularNameErrors: {}, //Object of spelling mistake:proper key:value pairs
populateJSON: function (destinationName, source, callback) {
$.getJSON(source, function (data) {
myApp[destinationName] = data;
alert(Object.keys(data)); //3
callback();
});
}
};
However, I see you are using the promise pattern. A promise should not be used as a simple notifier ("now the data has arrived somewhere"), but it should represent the data itself ("now, here's the data") - resolve it with the data instead of storing the data in a global variable/property and resolving with nothing. Actually, the $.ajax does already return such a promise so you don't have to do much for it:
var myApp = {
init: function () {
myApp.regularNameErrors = $.getJSON('data/spelling.json');
myApp.regularNameErrors.done(function(data) {
alert(Object.keys(data)); //3
alert(data['kingscross']); //1
});
}
};

Related

How can an object literal's property be assigned to the return value of one of its methods?

So I am making a game to practice using the mvc paradigm. I create an object literal for my model and want the functions it needs to generate values for its properties to be in the object itself. I have tried this with no success. I have tried using "this" when calling the function and not using it. Either way, I get a function not defined error from chrome. What can I do to fix this? Here's the relevant code:
var model = {
genPlayers: function() {
return tempPlayerArray;
},
playerArray: genPlayers()
}
You can't do that. 1. there is no function with the name genPlayers() only model.genPlayers() and 2. object properties can't be accessed during object initialization.
What you could do is:
var model = {
genPlayers: function() {
return tempPlayerArray;
},
playerArray: null
};
model.playerArray = model.genPlayers();
Or if model.genPlayers always only returns the tempPlayerArray you could do this:
var model = {
genPlayers: function() {
return tempPlayerArray;
},
playerArray: tempPlayerArray
};

Knockout Mapping Object Not Returned by Durandal Viewmodel

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.

init function and the object in Javascript

I have the following (simplified) code:
var Foo = (function () {
var data = {},
settings = {
// default settings here
};
function bar(callback) { // bar is an asynchronous function
var result = null;
// fiddle around until you get a result
if (callback) {
callback(result);
}
}
return {
init: function (options, callback) {
var kallback = callback;
$.extend(settings, options);
bar(function () {
if (kallback) {
kallback(WHAT_GOES_HERE);
}
});
},
debug: function () {
return {
settings: settings,
data: data
};
},
set: function (k, v) {
settings[k] = v;
},
get: function (k) {
return settings[k];
}
};
}());
The code above is in a js file, then in the footer of the page in question:
<script type="text/javascript">
Foo.init({ option1: "value", option2: "value" }, function (obj) {
console.log("The object was ", obj);
});
</script>
Basically, here is what I want to be able to do:
Create an object (with a set of optional params, but not important for this question)
During the creation phase of the object, have it call an asynchronous function
When the asynchronous function is done, I should be able to trigger a callback, and the argument for the callback should be the intialized object
I thought that this would work for WHAT_GOES_HERE above, but turns out, at least when I've tested it, that this is the DOM Window object.
First of all, am I constructing this object correctly? Or is there a better way to create it?
Secondly, assuming I am doing this right, what should go in the WHAT_GOES_HERE so that when console.log("The object was ", foo); runs, the value for obj is the created Foo object?
Yes, in an anonymous function called this way, this will refer to the window object. To refer to the this reference from the init method you have to store the reference in another variable:
var kallback = callback, self = this;
$.extend(settings, options);
bar(function () {
if (kallback) {
kallback(self);
}
});
Your way of constructing the object works, as long as you only want to have one single foo object.

Assigning scope amongst jQuery.getJSON and a JS.Class

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

Javascript function (type) to store & use data

I really never used a javascript function type or class before, I understand Java and Python, but not javascript. So, I build a class like this:
function FormStore (type) {
this.setup = () =>{
this.store = {};
this.ERR_LINE_PREFIX = '#err_';
this.NO_DISPLAY_CLASS = 'no-display';
this.settings = {
'myID':{'hide':false},
}
}
this.checkVal= () => {
var geoArr = ['id_xx','myID', (...)];
var id;
$.each( geoArr, function(val) {
id = geoArr[val];
console.log(this.store) //-> returns undefined, below line is error
if (!(this.store[id])) {
return false;
}
});
};
var FS = new FormStore();
FS.setup();
The store is filled by components on document.ready. There is a function that looks up if the aligned components (glyph, label, input) have some classes or values and for the specific component fills a dict: {label:false,glyph:false, input:false}. However, for some reason it doesn't matter. Even if I enter some values in to the store right away (in setup) or create them on the fly, in checkVal the store doesn't exist, it's undefined.
Please, anybody, what am I not understanding about javascript type and classes here? I am googling this a lot and trying to find good resources but, "javascipt variable class" (or type) just yields a lot of DOM manipulation.
edit
There is a context problem in checkVal, you are using a non-arrow (and not explicitly bound) callback function and trying to access this inside of it. Change that to an arrow function as well, and the parent context (this) will be preserved:
$.each( geoArr, (val) => {
id = geoArr[val];
console.log(this.store)
if (!(this.store[id])) {
return false;
}
});
And while you are at changing that section, it's not going to work. You will not get access to $.each's return value. You should rely on native array APIs for this task and use Array.every to determine if all geoArr items are in the store (assuming that's your goal):
// returns false if not all geoArr items are in the store
geoArr.every(id => this.store[id])
original
I don't see you calling checkVal() anywhere, but based on the error you are getting it is called prior to setup() (since setup initializes the store). You could solve that problem straight away by moving this.store = {} out of setup (right at the top), e.g.:
function FormStore(type) {
this.store = {};
...
Having said that, I would suggest either defining your methods on the prototype, or utilizing ES6 classes. Here is a simplified version of both:
ES5 class
function FormStore(type) {
// make sure user didn't forget new keyword
if (this === window) {
throw new Error('FormStore must be called with "new" keyword')
}
// initialize state, this is the constructor
this.type = type;
this.store = {};
// any other state the class manages
}
FormStore.prototype = {
setup: function() {
// do setup stuff
// "this" points to instance
console.log('setup', this.type)
},
checkVal: function() {
}
}
var formStore = new FormStore('foo')
console.log(formStore.store) // <-- not undefined
formStore.setup()
ES6 Class
class FormStore {
constructor(type) {
this.type = type;
this.store = {};
}
setup() {
console.log('setup', this.type)
}
checkVal() {
}
}
const formStore = new FormStore('bar')
console.log(formStore.store) // <-- not undefined
formStore.setup()
It has to do with scoping. Your $.each in checkVal has a normal function. Inside the function the scope if this is different. If you want to keep the original scope you could use a fat arrow function like you do when defining the methods.
this.checkVal= () => {
var geoArr = ['id_xx','myID', (...)];
var id;
$.each( geoArr, val => {
id = geoArr[val];
console.log(this.store) //-> returns undefined, below line is error
if (!(this.store[id])) {
return false;
}
});
}
When you run your original code and place a breakpoint on the line with console.log you can see in the inspector that this is set to the Window object and no longer points to your FormStore.
function FormStore () {
this.setup = function(){
this.store = {};
this.ERR_LINE_PREFIX = '#err_';
this.NO_DISPLAY_CLASS = 'no-display';
this.settings = {
'myID':{'hide':false},
}
}
this.checkVal= function(){
var geoArr = ['id_xx','myID'];
var id;
$.each( geoArr, function(val) {
id = geoArr[val];
console.log(this.store) //-> returns undefined, below line is error
if (!(this.store[id])) {
return false;
}
});
}
};
var FS = new FormStore();
FS.setup();
Works absolutely fine, the code you provided had a missing bracket and you were using some broken es6 syntax

Categories

Resources