After following a google maps tutorial on tuts+, I have decided to build few of my custom functions. Link
In the 'controlZoom' function I am trying to set up some custom controls however I cannot access the 'this.gMap':
controlZoom:function(){
var plusZoom = document.getElementsByClassName('icon-plus-sign')[0],
minusZoom = document.getElementsByClassName('icon-minus-sign')[0],
count = this.gMap.getZoom();
plusZoom.addEventListener('click', function() {
this.gMap.zoom(count++);
});
minusZoom.addEventListener('click', function() {
this.gMap.zoom(count--);
});
}
i can access the following:
console.log(count);
but not inside the 'click'event.
I am calling my custom function here: link
When I try to click I get the following error in the console:
'Cannot read property 'zoom' of undefined '
'this' inside your event listeners is probably the plus/minus button that was clicked. You can fix this by using a 'self' variable:
controlZoom:function(){
var self = this;
var plusZoom = document.getElementsByClassName('icon-plus-sign')[0],
minusZoom = document.getElementsByClassName('icon-minus-sign')[0],
count = this.gMap.getZoom();
plusZoom.addEventListener('click', function() {
self.gMap.zoom(count++);
});
minusZoom.addEventListener('click', function() {
self.gMap.zoom(count--);
});
}
or using .bind:
controlZoom:function(){
var plusZoom = document.getElementsByClassName('icon-plus-sign')[0],
minusZoom = document.getElementsByClassName('icon-minus-sign')[0],
count = this.gMap.getZoom();
plusZoom.addEventListener('click', function() {
this.gMap.zoom(count++);
}.bind(this));
minusZoom.addEventListener('click', function() {
this.gMap.zoom(count--);
}.bind(this));
}
These fixes assume that 'this' inside controlZoom is the object that has gMap! I think it is, because you say the this.gMap.getZoom() line is returning the correct count. So my two suggestions should both work, but if not, try debugging by adding
console.debug(this)
to check that 'this' is what you expect.
A note about using ++
++ is an operator, and count++ will increment count and return. But the value passed into the function will be that of count before it was incremented. You can convince yourself of this via the following console session:
var n = 0
function report(p) { console.log(p) }
report(n++)
0
You call the 'report' function with n++, which you would think might lead it to print out '1'. In fact it actually prints out '0'. This is because n is passed into report before it is incremented by ++.
In your case, the first time you call your zoom(count++) function, you are in fact calling it with the existing value of count, and then only afterwards is count incremented. So it appears as if you need two clicks to zoom in. The safe way to do this is:
plusZoom.addEventListener('click', function() {
count++;
this.gMap.zoom(count);
}.bind(this));
then you will be sure that count is incremented before you pass it to the zoom function.
You should use setZoom method instead:
this.gMap.setZoom(count++);
And you need to save reference to this and remove bind's:
var self = this,
plusZoom = document.getElementsByClassName('icon-plus-sign')[0],
// ...
and then
self.gMap.setZoom(count++);
It will work then.
In the scope of the function, gMap is not defined. Undefined variables do not have any properties, thus your zoom property won't work.
gMap is currently only defined in your prototype, and the functions that are a part of it, such as your controlZoom. When you add the click event listener's, you're not passing in gMap, and those functions do not belong to the prototype, so this.gMap won't work.
You'd have to define gMap in a global, or reference it using the prototype Mapster.
Related
I have a GeneralWrapper object that calls the statically-defined functions in the Library1 and Library2 objects.
The aim is that by calling GeneralWrapper.someFunc(), this will also call Library1.someFunc() and Library2.someFunc() without me having to explicitly create a function in GeneralWrapper called someFunc.
I attempt to implement this in the __preamble method below:
var GeneralWrapper = {
__modespopulated: false,
__validmodes: { // All 3 of these
spoonFunc: 1, // functions exist
knifeFunc: 1, // in Library1 and
forkFunc: 1 // Library2
},
__switchMode: function(funcname){
if (funcname in GeneralWrapper.__validmodes){
console.log("calling function", funcname)
GeneralWrapper.__preamble()
Library1[ funcname ](); // Call mode in Library1
Library2[ funcname ](); // Call mode in Library2
}
},
/* Attach valid modes to General Wrapper at runtime */
__preamble: function(){
if (!GeneralWrapper.__modespopulated)
{
for (var mode in GeneralWrapper.__validmodes)
{
GeneralWrapper[mode] = function(){
GeneralWrapper.__switchMode(mode)
};
}
GeneralWrapper.__modespopulated = true
}
GeneralWrapper.__otherprestuff();
},
__otherprestuff: function(){
// Stuff
},
funcThatAlwaysGetsCalled: function(){
GeneralWrapper.__switchMode("forkFunc");
}
}
var Library1 = {
forkFunc(){console.log("Lib1","fork")},
spoonFunc(){console.log("Lib1","spoon")},
knifeFunc(){console.log("Lib1","knife")}
}
var Library2 = {
forkFunc(){console.log("Lib2","FORK")},
spoonFunc(){console.log("Lib2","SPOON")},
knifeFunc(){console.log("Lib2","KNIFE")}
}
// Okay, let's initialise the object
GeneralWrapper.funcThatAlwaysGetsCalled();
For some reason calls to GeneralWrapper.spoonFunc() and GeneralWrapper.knifeFunc() always defer to the Fork output.
I imagine the problem stems from the anonymous function assignment on the GeneralWrapper[mode] = blah line where JS treats it as the same function each time, but I don't know how to get around this.
Please advise.
Solution:
Change this line:
for (var mode in GeneralWrapper.__validmodes)
into this:
for (let mode in GeneralWrapper.__validmodes)
Explanation:
what happens in your code (when binding functions in __preamble's loop) is that you create an anonymous function, which is totally fine. The problem is, your anon function has received the mode as a reference to local variable, so it's value is not automatically cloned but rather accessed at runtime. The main problem is that you've used var keyword, which means "hoisted variable" (it gets declared at the top of the function it was defined inside, even if it's somewhere in the middle of your function's code). In this scenario, you need a "block-scoped" variable, which will be bound to each loop iteration separately.
You can read more about variables hostings on MDN:
var at MDN
let at MDN
One thing you have to know - let was introduced in ES2015, so if you worry about backward compatibility with older browsers, you either have to use Function.prototype.bind or IIFE
One potential problem here is that you're creating functions inside a loop which can lead to some performance problems or unexpected behavior.
I'd replace:
for (var mode in GeneralWrapper.__validmodes)
{
GeneralWrapper[mode] = function(){
GeneralWrapper.__switchMode(mode)
};
}
with:
for (var mode in GeneralWrapper.__validmodes)
{
GeneralWrapper[mode] = GeneralWrapper.__switchMode.bind(this, mode);
}
Which should solve the problem at hand.
I'm trying to wrap my head around using Javascript prototype objects and have run into a block perhaps due to my understanding of a traditional classes.
My code looks like the following:
$(document).ready(function () {
var instance = new cards();
$dom = instance.buildItem(5);
}
var cards = function () {
//constructor
this.chromaObj = chroma.scale(["lightblue", "navy"]).domain([2, 6]);
}
cards.prototype.buildItem = function (val) {
var scale = this.chromaObj;
var $item = $("<span>" + val + "</span>").addClass("label-default").addClass("label").css({
"background-color": scale(val)
});
return $item;
}
Every time scale is called in the buildItem function, I receive an error in the console that scale is not a function, however, if I create the scale instance inside of the buildItem function, it works as expected.
Can someone please point me in the right direction as to why I am unable to access the function reference when it is defined in the constructor?
The original code does in fact work correctly.
My problem turned out to be that I was calling buildItem from another method (not shown here) using the generic cards.prototype.buildItem(), which caused a subsequent call to chromaObj to be undefined.
The solution was to change all calls to cards.prototype.buildItem() to this.buildItem().
I have 2 JS files - one with the functions I would like to access and the other that I'd like to call the function with.
(function($) {
var Place = function() {
var Location = function(id, duration, check) {
//Should access this function
}
}
})(jQuery);
I'm trying to access it with:
Place.Location(markerId, 600);
But all I'm getting is that it's not defined. Simple issue but can't quite figure this one out.
As it's a jQuery plugin, maybe there's a way I can access it via another method?
$.fn.place = function(params) {
var len = this.length;
return this.each(function(index) {
var me = $(this),
key = 'place' + (len > 1 ? '-' + ++index : ''),
instance = (new Place).init(me, params);
});
};
The way you are defining Location, it is a private variable inside the function Place. If you want to access it as an attribute of Place, you should replace var Location = ... with this.Location = ...
It's going out of scope. Because you wrapped your Place object in function($) {}, now anything outside that wrapper will no longer have access to variables inside the wrapper. If $ stands for jQuery, it should be a global anyways and you can take the wrapper out.
The solution is a combination of the other two answers.
You define Place as a variable in the (anonymous) function. It can't be used outside the scope of that function. (This function doesn't use jQuery, either, so the wrapper is unnecessary).
Place is a function. It executes code that sets local variable Location to a function, but doesn't export that function, so Location() is inaccessible outside the Place function.
You probably mean to make Place an object (instead of a function), and give it a Location method. Here's one way to write it:
var Place = {
Location: function(id, duration, check) {
// do something with id, duration, & check
}
};
// execute
Place.Location(someId, someDuration, someCheck);
(It doesn't look like you've posted all your code, like the Place.init() method, but there are plenty of ways to write this so that it works correctly; this should solve your immediate problem.)
While this issue occurred to me specifically with KnockoutJS, my question is more like a general javascript question.
It is good to understand however that ko.observable() and ko.observableArray() return a method so when assigning a value to them, you need to call the target as method instead of simply assigning a value to them. The code that I'm working with should also support plain objects and arrays, which I why I need to resolve to a method to call to assign a value to the target.
Think of these 2 examples:
Non-working one (this context changed in called method):
// Assigning value to the target object
var target;
// target can be of any of thr following types
target = ko.observableArray(); // knockout observable array (function)
// target = ko.observable(); // knockout observable (function)
// target = {}; // generic object
// target = []; // generic array
//#region resolve method to call
var method;
if (isObservable(target)) {
// if it is a knockout observable array, we need to call the target's push method
// if it is a konckout observable, we need to call the target as a method
method = target.push || target;
} else {
// if target is a generic array, we need to use the array's push prototype
// if target is a generic object, we need to wrap a function to assign the value
method = target.push || function(item){ target = item; };
}
//#endregion
// call resolved method
method(entity);
Working one (this context is fine):
if (isObservable(target)) {
if (target.push) {
target.push(entity);
} else {
target(entity);
};
} else {
if (target.push) {
target.push(entity);
} else {
target = entity;
};
}
Now, to the actual question:
In the first approach, later in the execution chain when using a knockout observable knockout refers to this context within itself, trying to access the observable itself (namely this.t() in case someone is wondering). In this particular case due to the way of callin, this has changed to window object instead of pointing to the original observable.
In the latter case, knockout's this context is just normal.
Can any of you javascript gurus tell me how on earth my way of calling can change the 'this' context of the function being called?
Ok, I know someone wants a fiddle so here goes :)
Method 1 (Uncaught TypeError: Object [object global] has no method 'peek')
Method 2 (Works fine)
P.S. I'm not trying to fix the code, I'm trying to understand why my code changes the this context.
UPDATE:
Thanks for the quick answers! I must say I hate it when I don't know why (and especially how) something is happening. From your answers I fiddled up this quick fiddle to repro the situation and I think I got it now :)
// So having an object like Foo
function Foo() {
this.dirThis = function () {
console.dir(this);
};
};
// Instantiating a new Foo
var foo = new Foo();
// Foo.dirThis() has it's original context
foo.dirThis(); // First log in console (Foo)
// The calling code is in Window context
console.dir(this); // Second log in console (Window)
// Passing a reference to the target function from another context
// changes the target function's context
var anotherFoo = foo.dirThis;
// So, when being called through anotherFoo,
// Window object gets logged
// instead of Foo's original context
anotherFoo(); // 3rd log
// So, to elaborate, if I create a type AnotherFoo
function AnotherFoo(dirThis){
this.dirThis = dirThis;
}
// And and instantiate it
var newFoo = new AnotherFoo(foo.dirThis);
newFoo.dirThis(); // Should dir AnotherFoo (4th in log)
If you're after a way to choose the 'this' that will get used at the time of call,
you should use bind, that's exactly done for that.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
So if SomeObject has a push method, then storing it like this won't work :
var thePushMethod = someObject.push;
since you loose the context of the function when writing this.
Now if you do :
var thePushMethod = someObject.push.bind(someObject);
the context is now stored inside thePushMethod, that you just call with
thePushMethod();
Notice that you can bind also the arguments, so for instance you might write :
var pushOneLater = someObject.push.bind(someObject, 1 );
// then, later :
pushOneLater(); // will push one into someObject
Consider this example,
function Person () {
this.fname = "Welcome";
this.myFunc = function() {
return this.fname;
}
};
var a = new Person();
console.log(a.myFunc());
var b = a.myFunc;
console.log(b());
Output
Welcome
undefined
When you make a call to a.myFunc(), the current object (this) is set as a. So, the first example works fine.
But in the second case, var b = a.myFunc; you are getting only the reference to the function and when you are calling it, you are not invoking on any specific object, so the window object is assigned. Thats why it prints undefined.
To fix this problem, you can explicitly pass the this argument with call function, like this
console.log(b.call(a));
So, for your case, you might have to do this
method.call(target, entity);
Check the fixed fiddle
I am trying to write a script that does a slide show. I can do it with functions, but I want to use the prototype method. What I am having a hard time figuring out is the procedure. Here is what I have tried to do
var displayVars = {
slide: '',
thumb: ''
}
//setup display
display = function(slide,thumb) {
displayVars.slide = $(slide);
displayVars.thumb = $(thumb);
// set slider width
}
display.prototype.play = function() {
// move slide to this location
display.hightlight();
}
display.prototype.hightlight = function() {
// add border to element
}
$(function() {
newdis = new display('.show-slide','.window-thumbs');
displayVars.timer = setTimeout(newdis.play,500);
});
If you notice in the play function I want to call the highlight method. What I really want is to run the highlight function every time the play function is called. I can't get my head to see how this can be done because "display" or "this" will not let me access the highlight function.
The problem is not with the innards of your prototype functions, but rather with the way you set up the timeout handler.
displayVars.timer = setTimeout(function() { newdis.play(); }, 500);
Then you'll be able to use this in the "play" function:
display.prototype.play = function() {
// move slide to this location
this.hightlight();
}
There's no intrinsic "membership" relationship between a function and an object of any sort. Object properties can refer to functions, but the only time that means anything is when a function call is made via the object property reference. Since you weren't calling the function, but just grabbing a reference to it to pass to "setTimeout()", there was nothing to set the value of this. By wrapping it in an anonymous function that explicitly calls "play" via the object reference, you set up this correctly.
Another way to do this is with the "bind()" function available in newer browsers:
displayVars.tinmer = setTimeout(newdis.play.bind(newdis), 500);
That will have more-or-less the same effect as the anonymous function (with some extra subtleties that don't make much difference most of the time).