Javascript Object Literal Scope Issue? - javascript

I'm having a hard time understanding why this.$map and this.markers are undefined. Here's the code I'm working with and I have added comments where I expect these variables to supply a value:
(function($) {
'use strict';
var A = {
/**
* Initialize the A object
*/
init: function() {
this.$map = this.renderMap();
this.markers = this.getMarkers();
this.renderMarkers();
},
renderMap: function() {
var url = 'http://a.tiles.mapbox.com/v3/delewis.map-i3eukewg.jsonp';
// Get metadata about the map from MapBox
wax.tilejson(url, function(tilejson) {
var map = new L.Map('map', {zoomControl: false});
var ma = new L.LatLng(42.2625, -71.8028);
map.setView(ma, 8);
// Add MapBox Streets as a base layer
map.addLayer(new wax.leaf.connector(tilejson));
return function() {
return map;
};
});
},
renderMarkers: function() {
var geojsonLayer = new L.GeoJSON(null, {
pointToLayer: function (latlng){
return new L.CircleMarker(latlng, {
radius: 8,
fillColor: "#ff7800",
color: "#000",
weight: 1,
opacity: 1,
fillOpacity: 0.8
});
}
});
geojsonLayer.addGeoJSON(this.markers); // this.markers is undefined
this.$map.addLayer(geojsonLayer); // this.$map is undefined
},
getMarkers: function() {
$.getJSON("/geojson/", function (data) {
return data;
});
}
};
/**
* A interactions
*/
$(document).ready(function() {
A.init()
});
})(jQuery.noConflict());
I have spent much of the day searching and I think I'm missing something fundamental here, but I don't get it.

Neither the renderMap, nor getMarkers methods return any value, consequently their return value is undefined.
It looks like you are trying to initialize these fields from an ajax request, which is not necessarily a good idea.
What you probably ought to do is something like:
getMarkers: function(callback){
var result = this;
$.getJSON("url", function(jsonData){
result.markers = jsonData;
if(callback) callback()
});
},
which will lazily initialize the fields of the object as they become available.
NB: AJAX is asynchronous you cannot rely on this callback setting the member quickly, or indeed ever (it could fail).
This suggests you need to think a bit more about your design, and try to use callbacks more.
e.g. modify the getMarkers and renderMap functions as above to take a callback that is called after the data is stored then change init to:
init: function(){
var res = this;
var post_init_callback = function(){
if(res.$map != undefined && res.markers!=undefined){
//this will only run after both ajax calls are complete
res.renderMarkers();
}
};
this.getMarkers(post_init_callback);
this.renderMap(post_init_callback);
},

The problem here is that you call return inside another function. What you're essentially doing is defining getMarkers (the simplest example) as this:
getMarkers: function() {
$.getJSON('/geojson/', some_random_func);
}
At which point it becomes obious that getMarkers doesn't actually return anything (ergo undefined). The same goes for your renderMap function. Also in this case your "some_random_func" is defined as function(data) { return data; } but what does it return it to? Truth is that the some_random_func is called by jQuery itself, and AFAIK jQuery doesn't care at all for the return-value of it's success-function.

Related

How do I initially load an observable array asynchronously

This is my viewmodel:
function PitchViewModel() {
var self = this;
self.selectedPitch = ko.observable();
self.pitches = ko.computed(function () {
return $.getJSON("/api/Pitch", function (data) {
var obs = ko.mapping.fromJS([])
ko.mapping.fromJS(data, {}, obs) ;
// seems to work, but somehow observables are changed back into objects after binding
return obs();
})}, this).extend({ asyncArray: [{ Id: 1, PitchNumber: 1, Length: 0, Width: 0, HasElectricity: false, Name: "Test" }] });
// behaviours
self.selectPitch = function () {
console.log("inside selectPitch");
self.selectedPitch(this);
}
}
I'm using an async extender as shown here: asynchronous computed observables
adapted a little bit for observablearrays like so (in line 3):
var plainObservable = ko.observableArray(initialValue), currentDeferred;
In my view i do this:
var domNode = $('#content')[0];
var pitchViewModel = new PitchViewModel();
ko.applyBindings(pitchViewModel, domNode);
It seems to work fine. The binding happens asynchronously. Pretty cool so far.
However!
When (in Chrome) I put a breakpoint on
return obs();
the obs() function is an observableArray and has objects with observable properties.
But when I break on
console.log("inside selectPitch");
and inspect self.pitches() it has become a 'normal' array with objects the have 'normal' (not observable) properties.
What am I missing here?
BTW: I have tried using a an observableArray for self.pitches instead of the computable. But then the ko.applybindings happens before the initialization of the observable array, leading to binding errors.
Thanks for your help.
Frans
I have not tried to run your code but what I SUSPECT happens is that the extender uses the result of the ajax call returned as a Deferred/Promise. Your processing of the results happens in the callback function and is not used for anything afterwards.
You should not mix Deferreds and callbacks like this. Try the following instead:
self.pitches = ko.computed(function () {
return $.getJSON("/api/Pitch")
.then(function(data) {
return ko.mapping.fromJS(data);
});
}).extend({ asyncArray: [...] });

Javascript TypeError : i need explanation

I have a javascript code
Html5Template_300x250 = function(config) {
this.config = config;
var self = this;
adkit.onReady(this.init());
};
Html5Template_300x250.prototype = {
// Function That Creates Element Var
d: function(id) {
return document.getElementById(id);
},
// Initialize DCO HTML5 template
init: function() {
adkit.onReady(this.handleSVData);
},
handleSVData: function() {
var myData = adkit.getSVData("varName");
alert(myData);
this.startAd();
},
startAd: function(data) {
alert("test2");
}
}
In the above code i have used an external javascript adkit.js and using that method in my code. The initial method is started as
adkit.onReady(this.init());
It is calling a init function and which is then calling other methods including handleSVData which is getting a value from the json file which is in the root folder as
var myData = adkit.getSVData("varName");
The part of the code is working fine but after that line i am calling another method
this.startAd();
But this method is not working and i am getting error
TypeError: this.startAd is not a function
I am not good in javascript and giving me headaches can someone explain me why it is so complicated and what i am doing wrong here ??
When handleSVData is called by adkit it is called in the scope that is not an instance of Html5Template_300x250 - that is why this does not have startAd method.
As for adkit.onReady(this.init()); line.
adkit.onReady expects a function as a parameter. It stores this function variable and calls when it is time for onReady event. This is set correctly by adkit.onReady(this.handleSVData); line. this.init(), however, is a call to init function and your line adkit.onReady(this.init()); passes to adkit.onReady whatever init returns. But it does not return anything - you are passing undefined as parameter.
init: function() {
var template = this;
adkit.onReady(function(){
template.handleSVData();
});
},
And change line
adkit.onReady(this.init());
to
this.init();

KnockoutJS bindings are applied before my ViewModel is actually created

I think the way i put the question was misleading you, so i've made a major edit.
I will use code from knockoutjs tutorial
"Loading and saving data", step 3
I've made one change to show what i mean.
function Task(data) {
this.title = ko.observable(data.title);
this.isDone = ko.observable(data.isDone);
}
function TaskListViewModel() {
// Data
var self = this;
self.tasks = ko.observableArray([]);
self.newTaskText = ko.observable();
self.incompleteTasks = ko.computed(function() {
return ko.utils.arrayFilter(self.tasks(), function(task) { return !task.isDone() });
});
// Operations
self.addTask = function() {
self.tasks.push(new Task({ title: this.newTaskText() }));
self.newTaskText("");
};
self.removeTask = function(task) { self.tasks.remove(task) };
// ** Read this!!
// This below is the original code in the tutorial, used to fetch remote data.
// I commented this out, and i will use the below load() method instead.
// **
//$.getJSON("/tasks", function(allData) {
// var mappedTasks = $.map(allData, function(item) { return new Task(item) });
// self.tasks(mappedTasks);
//});
// This is the load method to emulate the above commented
// $.get. Please, DO NOT CARE about the implementation, or
// the setTimeout usage, etc., this method ONLY EXISTS TO
// EMULATE A SLOW SERVER RESPONSE.
// So, we have to ways of using it:
// - load('slow'), will set result after 1 second
// - any other argument will set result instantly.
self.load = function(howFast) {
if (howFast == 'slow') {
setTimeout(function(){
mappedTasks = [];
mappedTasks.push(new Task({
title: 'Some task slowly loaded from server',
isDone: false
}));
}, 1000);
} else {
mappedTasks = [];
mappedTasks.push(new Task({
title: 'Some task quick!',
isDone: false
}));
}
}
// Now please note this:
// - if i use load(), mappedTask is instant updated and
// everything runs fine
// - if i use load('slow'), mappedTask is updated AFTER
// VALUES ARE BOUND, so if you open your browser console
// you will see an "Uncaught ReferenceError: mappedTasks is not defined" error.
self.load();
self.tasks(mappedTasks);
}
ko.applyBindings(new TaskListViewModel());
PROBLEM: Bindings are applied AFTER the ViewModel has done with initializazions, therefore causing errors. I think i provided enough detail in code comments, ask me if you think you need more. Anyway, i am little amazed that nobody ever hit this thing before, so am i missing something VERY important here?
QUESTION: How to avoid this?
Fiddle
You have an html error.
Your setTimeout function is attempting to programmatically set the selected option to an option that isn't in the list. The browser can't do that and so the selection stays where it is.
http://jsfiddle.net/UD89R/6/
function ViewModel() {
// Setup something.
var self = this;
self.foo = ko.observable();
self.options = ko.observableArray([{id:1, name:'Homer'}, {id:2, name:'Barney'}]);
// Make a lot of async data load, like
// $.get('this', function(){ /* Process data */ });
// $.get('that', anotherHandler);
// $.get('somethingElse', self.someObservable);
// Assume the whole process would take 1 second.
setTimeout(function(){
self.options.push({id: 3, name: 'Grimes'});
self.foo(3);
// Too late! foo has been overriden by the select-options binding,
// so this one will not log 'Grimes' as expected.
console.log('Loading done, check foo value:' + self.foo());
}, 1000);
}

How can rewrite function instead of reference?

var BigObject = (function() {
function deepCalculate(a, b, c) {
return a + b + c;
}
function calculate(x) {
deepCalculate(x, x, x);
}
return {
calculate: calculate,
api: {
deepCalculate: deepCalculate
}
}
})();
This is basic self executing function with private function I keep in api.
The problem I have is that now I can't overwrite deepCalculate from the outside of the function.
How is that a problem? I use Jasmine and want to test if function was called. For example:
spyOn(BigObject, 'calculate').andCallThrough();
expect(BigObject.api.deepCalculate).toHaveBeenCalled();
fails. However as I debug, I am sure that Jasmine binds BigObject.api.deepCalculate as a spy, however from the inside calculate still calls original deepCalculate function and not the spy.
I would like to know how can I overwrite the function and not just a reference for it.
The simple answer would be:
(function ()
{
var overWriteMe = function(foo)
{
return foo++;
},
overWrite = function(newFunc)
{
for (var p io returnVal)
{
if (returnVal[p] === overWriteMe)
{//update references
returnVal[p] = newFunc;
break;
}
}
overWriteMe = newFunc;//overwrite closure reference
},
returnVal = {
overWrite: overWrite,
myFunc: overWriteMe
};
}());
Though I must say that, I'd seriously think about alternative ways to acchieve whatever it is you're trying to do. A closure, IMO, should be treated as a whole. Replacing parts of it willy-nilly will soon prove to be a nightmare: you don't know what the closure function will be at any given point in time, where it was changed, what the previous state was, and why it was changed.
A temporary sollution might just be this:
var foo = (function()
{
var calc = function(x, callback)
{
callback = callback || defaultCall;
return callback.apply(this, [x]);
},
defaultCall(a)
{
return a*a+1;
},
return {calc: calc};
}());
foo(2);//returns 5
foo(2,function(x){ return --x;});//returns 1
foo(2);//returns 5 again
IMO, this is a lot safer, as it allows you to choose a different "internal" function to be used once, without changing the core behaviour of the code.

Self invoking function via setTimeout within object

I would like invoke a method of an js object within the very same object method via setTimeout:
var ads = {
init: function() {
ads.display_ads();
},
display_ads: function() {
console.log('Displaying Ads');
setTimeout('ads.display_ads()', 5000);
}
}
However, I'm getting this error message:
ads is not defined
setTimeout('ads.display_ads()', 2000);
What am I missing here? How would i alter the string within the setTimeout function?
Thanks for your help!
Edit: I use firefox on mac.
Just change it to ads.display_ads, note that this is not a String. i.e.
var ads = {
init: function() {
ads.display_ads();
},
display_ads: function() {
console.log('Displaying Ads');
setTimeout(ads.display_ads, 5000);
}
}
As #FelixKling points out in his comment below, be careful about what this refers to in ads.display_ads. If ads.display_ads is called via ads.init() or ads.display_ads() this will be the ads Object. However, if called via setTimeout this will be window.
If the context is important though, you can pass an anonymous function to setTimeout, which in turn calls ads.display_ads():
setTimeout(function() {
ads.display_ads();
}, 5000);
or
var self = this;
setTimeout(function() {
self.display_ads();
}, 5000);
try this.display_ads,
I'd recommend you to use this for referencing ads
so the code will be like:
var ads = {
init: function() {
this.display_ads();
},
display_ads: function() {
console.log('Displaying Ads');
setTimeout(this.display_ads, 5000);
}
}
So, like jakeclarkson said, ads.display_ads:
setTimeout(hitch(ads, ads.display_ads), 5000);
The difference is that you should use a "hitch" function:
function hitch(scope, callback) {
return function () {
return callback.apply(scope, Array.prototype.slice.call(arguments));
}
}
This function will ensure that the scope of the callback is your ads object. See MDN for a description of the apply function:
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/apply
The answer of jabclab helped me greatly. I was trying to get a game loop to work, but it seems this was referencing window instead of the object I created. Here is a minimal version of the now running code (it just counts up each second and writes it into a div "content"):
function Game(model, renderer){
this.model = model;
this.renderer = renderer;
this.run = function(){
this.model.update();
this.renderer.draw(this.model);
var self = this;
setTimeout(function(){self.run();}, 1000);
};
}
function Model(){
this.data = 0;
this.update = function(){
this.data++;
};
}
function Renderer(){
this.draw = function(model, interpolation){
document.getElementById("content").innerHTML = model.data;
};
}
var game = new Game(new Model(), new Renderer());
game.run();
Instead of setTimeout(this.run, 1000) I used self instead of this to clarify which object is meant (as suggested by jabclab). Thought I'd add this because I'm using an object constructor and has a slightly different syntax. Especially using ads.method didn't work (because Game is not an object yet I guess), so I had to use the last solution.

Categories

Resources