Backbone trouble with calling functions - javascript

I'm very inexperienced with javascript so forgive me if this is really obvious...
I'm using the TodoMVC example found here (http://todomvc.com/architecture-examples/backbone/) as a foundation for a Cordova project. The trouble that I'm having is combining this with the Camera and Geolocation plugins, I get errors calling any functions in js/views/app-view.js from within the Camera/Geolocation callback functions. I'm assuming it's a problem of context but I don't know enough about javascript to fix it.
For example, the TodoMVC project adds new items using this line of code, in js\views\app-view.js:
app.todos.create(this.newAttributes());
I'm trying to combine this with the camera function to take a picture with a phone's camera, then add a new todo item with the picture stored in it. Everything works fine except when I try to have that above line of code in the camera callback function, at the bottom like this:
onCameraSuccess: function(imageData) {
// Stores image data in a hidden field to be used later. Not best method but it works
document.getElementById('imageData').value = "data:image/jpeg;base64," + imageData;
// This line should create a new item
app.todos.create(this.newAttributes());
},
It gives the error "Undefined is not a function", seemingly referring to "this.newAttributes()". Again it seems like this is a matter of context, because that very same line works almost anywhere else in the same script.
OnCameraSuccess is a callback function from the Camera's getPicture function:
Camera: function() {
navigator.camera.getPicture(this.onCameraSuccess, this.onCameraFail, { quality: 50,
destinationType: Camera.DestinationType.DATA_URL,
targetWidth: 250,
targetHeight: 250,
correctOrientation: true
});
},
Likewise, a function for Geolocation requires two callback functions, for success and errors, but I can't find an acceptable way to refer to the other functions that doesn't give a TYPE_MISMATCH_ERR that says those aren't functions.
onDeviceReady: function() {
//This line calls the geolocation function and specifies the two callback functions, but it thinks they don't exist
navigator.geolocation.watchPosition(this.onLocationSuccess, this.onLocationError, {enableHighAccuracy : true});
},
onLocationSuccess: function(position) {
// map logic
},
onLocationError: function(error) {
alert('code: ' +error.code+ '\n' + 'message: ' +error.message + '\n');
},
They're right below the onDeviceReady function, but for some reason it can't find them. this.onLocationSuccess doesn't work (even though that's how it refers to all of its other functions), self.onLocationSuccess doesn't work... Again, it seems like a matter of context, but I can't figure it out.
Any ideas? I don't even know enough about javascript to know if I'm explaining this adequately, so feel free to yell at me about that too.

The value of this in JavaScript depends on how a function is called. For example, this:
var obj = {
f: function() { console.log(this) }
};
obj.f();
will put obj in the console but this:
var obj = {
f: function() { console.log(this) }
};
var f = obj.f;
f();
will (usually) dump window into the console even though the same function is being called. This this behavior confuses almost everyone when they start using JavaScript. In your case, the documentation doesn't say anything about what this will be in the getPicture callbacks so this is probably going to be window instead of your object.
If you need a particular this with a callback function in JavaScript, you usually have to arrange it yourself. One way to do that is to stash the desired this in a variable and use an anonymous function:
var _this = this;
navigator.camera.getPicture(
function(imageData) { _this.onCameraSuccess(imageData) },
...
);
A cleaner way (especially when there are arguments to your functions) is to bind the function to the desired this using $.proxy, _.bind, Function.prototype.bind, or similar utilities that come with pretty much every JavaScript toolkit. For example, given this:
var obj = {
f: function() { console.log(this) }
};
var f1 = $.proxy(obj.f, obj);
var f2 = _(obj.f).bind(obj);
var f3 = obj.f.bind(obj);
Calling f1(), f2(), or f3() will all dump obj into the console. Underscore is the typical toolkit with Backbone so this would be common:
navigator.camera.getPicture(
_(this.onCameraSuccess).bind(this),
_(this.onCameraFail).bind(this),
{ ... }
);
Underscore also provides _.bindAll, this is commonly used within initialize to bind several functions in-place:
initialize: function() {
_.bindAll(this, 'onCameraSuccess', 'onCameraFail');
//...
}
and then you can use this.onCameraSuccess without worry about the binding:
navigator.camera.getPicture(
this.onCameraSuccess,
this.onCameraFail,
{ ... }
);
Using _.bindAll in initialize would probably be the most common approach in Backbone.

Since you are passing this as a reference to the camera function it is being overridden by camera's context with the current context of the application.
Make a clone of the current context like
var currentContext = this;
currentContext.onCameraSuccess,
currentContext.onCameraFail
This is applicable where ever the application control is moving from one context to other eg:backbone view to underscore functions and moving back to callback function etc.

Related

Javascript this vs using same var name within itself

I am new to Javascript, can someone please help me understand if there is a fundamental difference between these 2 ways
First where I use this to call a function defined inside the var itself
var TxMmm={
name: {},
timeout: 2000,
testFunc1: function(){
console.log("testFunc1");
testFunc2();
this.testFunc3();
},
testFunc3: function(){
console.log("Test func3");
}
}
function testFunc2(){
console.log("This is func2 is outside var");
}
v/s Below where I use the var TxMmm to call function defined inside itself.
var TxMmm={
name: {},
timeout: 2000,
testFunc1: function(){
console.log("testFunc1");
testFunc2();
TxMmm.testFunc3();
},
testFunc3: function(){
console.log("Test func3");
}
}
function testFunc2(){
console.log("This is func2 is outside var");
}
In that specific code there isn't that much difference, because the object is a singleton. Some differences:
In your first code block using this, this could refer to something other than the TxMmm object in (say) testFunc1, depending on how testFunc1 is called. For instance, this would fail:
const fn = TxMmm.testFunc1;
fn();
But in your second code block, TxMmm will refer to the object (unless the next point comes into play), so that would work. More here and here.
If someone does:
const x = TxMmm;
TxMmm = somethingElse;
x.testFunc1();
...your first code block using this will keep working, because this is set by how testFunc1 is called. But your second code block using TxMmm would fail, because TxMmm doesn't point to the object anymore.
It almost never matters (seriously, very nearly never), but in your second code block, when you use TxMmm, the JavaScript engine has to look up that identifer to find it in the enclosing scope. Your code using this is able to resolve it immediately (since those aren't arrow functions), which in very rare situations can be faster in a noticeable way.
For non-singleton objects, this can be very important, since it tells the code which object's properties to use.

Dojo - Promise Can't Access Variables

I am working with dojo in the ESRI Web App Builder and have come across a situation where I need to run an AJAX call and still access a variable from the base class. Below is my code with comments in it to explain exactly where it is successful and exactly where it is failing:
define(['dojo/_base/declare', 'jimu/BaseWidget', 'dojo/request', "esri/layers/WMSLayer", "esri/config", "dojo/domReady!"], function (declare, BaseWidget, request, WMSLayer, esriConfig) {
return declare([BaseWidget], {
baseClass: 'jimu-widget-mywidget',
// This function is called by a button press (Normally the WMSLayer variable would be set by user input)
addWms: function () {
var wmsLayer = new WMSLayer("http://sampleserver1.arcgisonline.com/ArcGIS/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/WMSServer", {
format: "png",
visibleLayers: [2]
});
this.map.addLayer(wmsLayer); // this.map is inherited from BaseWidget as far as I can see. This adds a wms to my map without error
request("request.html").then(function(data){
var wmsLayer = new WMSLayer("http://sampleserver1.arcgisonline.com/ArcGIS/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/WMSServer", {
format: "png",
visibleLayers: [2]
});
this.map.addLayer(wmsLayer); // This is now in another context....I get the error HERE.
// At this point map is not defined because this anonymous function is running
// in a different context. ( at least I think that's what is happening )
}, function(err){
// Hopefully there are no typos in my example XD
});
}
});
});
My question is --> How do I access the "map" variable from withing the callback function of "request"?
I want to be able to run this.map.addLayers from my call to the GetCapabilities of a WMS service. The request would normally call it and I get all the way to the end of my code until I can't access the "map" variable anymore as I know it.
Dojo type answer is preferred but plain old javaScript is also fine. Please avoid JQuery answers.
Resources are:
ESRI JavaScript library
Dojo
ESRI Web App Builder
The problem you're running into is the classic problem of execution context being lost when an asynchronous callback is invoked (and thus this no longer means what you want). There are generally two ways to resolve this.
One way is to create a variable in the outer scope that references what this is, so that the inner function can access it:
var self = this;
request('request.html').then(function (data) {
// ...
self.map.addLayer(wmsLayer);
});
The other way is to use context binding, either via Function#bind (ES5) or dojo/_base/lang.hitch:
// Function#bind:
request('request.html').then(function (data) {
// ...
this.map.addLayer(wmsLayer);
}.bind(this));
// lang.hitch:
request('request.html').then(lang.hitch(this, function (data) {
// ...
this.map.addLayer(wmsLayer);
}));
It's also common with the binding approach to break out the asynchronous handler function to another internal instance method:
_addRequestedLayer: function () {
// ...
this.map.addLayer(wmsLayer);
},
addWms: function () {
// ...
// ES5:
request('request.html').then(this._addRequestedLayer.bind(this));
// ...or lang.hitch, with late binding:
request('request.html').then(lang.hitch(this, '_addRequestedLayer'));
There is also a tutorial on lang.hitch.
In your request callback, this does not refer to your class anymore. A simple way to have access to the map in the callback is assigning the reference of the map in a variable on addWms function scope and then using it in your callback:
addWms: function() {
var map = this.map;
// Your code here
request('request.html').then(function (data) {
// Your code here
map.addLayer(wmsLayer); // Note that you're using map instead of this.map
}
}
You can also use Dojo hitch function, where you can pass a function and the context that it should be applied. I suggest that you take a look at this link http://dojotoolkit.org/reference-guide/1.10/dojo/_base/lang.html#hitch.

scope Issue seeing object methods

I have tried searching through a lot of S.O. pages but nothing has touched EXACTLY on this top while also NOT USING JQUERY.... I am trying to stick to pure JavaScript as I want to learn it 115% before advancing my current knowledge of JQuery.
I have an object called ScreenResizeTool like this...
function ScreenResizeTool(currImg) {
window.addEventHandler('resize', function() {
listen(currImg);
}, true);
}
and a method like this...
ScreenResizeTool.prototype.listen = function(currImg) {
//Random Code For Resizing
};
My trouble is probably obvious to an experienced JavaScript user but I am having trouble not making this into a messy dirty awful OOP set. I have done various tests to show and prove to myself that the this inside the addEventHandler changes when it becomes bound to the window. This much I assumed before testing but I was able to see that once window.resize event happens the listen method is gone and not a part of the global window variable....
I have also tried adding a this capture such as this.me = this inside the object constructor however it also couldn't see the me variable once it ran. Once the window took the function over it no longer knew anything about the me variable or any reference to my class methods....
I am aware that I could separate this differently but my goal here is to learn how to fully encapsulate and use as many clean OOP structures as possible as I just came from the .NET world and I need it in my life.
I am also aware that I could make messy calls and or store this object or access to the methods inside the window variable but that seems outright wrong to me. I should be able to fully encapsulate this object and have its events and methods all implemented in this class structure.
I also know that the currImg variable is not going to be seen either but lets start small here. I assume once I figure out my incorrect train of thought on scope for JavaScript I should be fine to figure out the currImg problem.
I know there's 1000 JavaScript programmers out there waiting to rip me a new one over asking this simple question but I gotta know...
Thoughts anyone?
this inside a function bound to a DOM Object (like window) will always refer to that object.
this inside a constructor function will always refer to the prototype.
A common practice to circumvent the this issue, as you mentioned, is to cache it in a variable, often called self. Now you want the variables and properties of your object available after instantiation, so what you need is the return keyword, more specifically to return the parent object itself. Let's put that together:
function ScreenResizeTool() {
var self = this;
// method to instantiate the code is often stored in init property
this.init = function() {
window.addEventListener('resize', function() {
self.listen(); // self will refer to the prototype, not the window!
}, true);
};
return this;
}
ScreenResizeTool.prototype.listen = function() { // Dummy function
var h = window.innerHeight, w = window.innerWidth;
console.log('Resized to ' + w + ' x ' + h + '!');
};
Pretty easy huh? So we have our prototype now, but prototypes can't do anything if there's not an instance. So we create an instance of ScreenResizeTool and instantiate it with its init method:
var tool = new ScreenResizeTool();
tool.init();
// every time you resize the window now, a result will be logged!
You could also simply store the listen & init methods as private functions inside your constructor, and return them in an anonymous object:
function ScreenResizeTool() {
var listen = function() { ... };
var init = function() { ... };
// in this.init you can now simply call listen() instead of this.listen()
return {
listen: listen,
init: init
}
}
Check out the fiddle and make sure to open your console. Note that in this case I'd rather use the first function than the second (it does exactly the same) because prototypes are only useful if you have multiple instances or subclasses
The whole concept of this in JavaScript is a nightmare for beginners and in my code I usually try to avoid it as it gets confusing fast and makes code unreadable (IMHO). Also, many people new to JavaScript but experienced in object-oriented programming languages try to get into the whole this and prototype stuff directly though the don't actually need to (google JS patterns like IIFE for example as alternatives).
So looking at your original code:
function ScreenResizeTool(currImg) {
window.addEventHandler('resize', function() {
listen(currImg); // global function listen?
}, true);
}
ScreenResizeTool.prototype.listen = function(currImg) {
//Random Code For Resizing
};
First off, you probably mean addEventListener instead. In its callback you refer to listen but as a global variable which would look for it as window.listen - which doesn't exit. So you could think to do this:
function ScreenResizeTool(currImg) {
window.addEventHandler('resize', function() {
this.listen(currImg); // what's this?
}, true);
}
As you want to use the prototype.listen function of ScreenResizeTool. But this won't work either as the event listener's callback function is called with a different this and not the this that is your function scope.
This is where something comes in which makes most programmers cringe, you have to cache this, examples from code I've seen:
var _this = this;
var that = this;
var _self = this;
Let's just use the latter to be able to refer to the function within the event callback:
function ScreenResizeTool(currImg) {
var _self = this;
window.addEventListener('resize', function() {
_self.listen();
}, true);
}
Now this will actually work and do what you want to achieve: invoke the prototype.listen function of ScreenResizeTool.
See this JSFiddle for a working example: http://jsfiddle.net/KNw6R/ (check the console for output)
As a last word, this problem did not have anything to do with using jQuery or not. It's a general problem of JS. And especially when having to deal with different browser implementations you should be using jQuery (or another such library) to make your own code clean and neat and not fiddle around with multiple if statements to find out what feature is supported in what way.

undefined of something that is clearly defined

this must be some JavaScript concept that I am not grasping yet..
For some odd reason the id of thisSound returns undefined! why??
console.log("o: "+o.id+" - "+decodeURI(soundURL));
// create sound
thisSound = soundManager.createSound({
id:o.id,
url:decodeURI(soundURL)
});
console.log("thisSound: "+thisSound.id+" - "+thisSound.url);
the console :
o: Sound0 - http://api.soundcloud.com/tracks/76673175/stream?client_id=e992d357c0914e9b65ba17f459720fc0
thisSound: undefined - http://api.soundcloud.com/tracks/76673175/stream?client_id=e992d357c0914e9c65ba17f459720fc0
The code you provided doesn't “obviously” define the id of the returned object.
// create sound
thisSound = soundManager.createSound({
id:o.id,
url:decodeURI(soundURL)
});
Let's say I write the function createSound for you:
var soundManager = {
createSound: function (options) {
// do internal magic here
createSound(options);
// return public API here
return {
getId: function () {}
}
}
};
So, my point here is that if there is a third-party function, you should follow the docs of whoever created that function, and SoundManager apparently doesn't return an object with id property defined on it. It “returns a SMSound object instance” – and what is that object, please find out in the docs.
I know nothing about the soundcloud library, although the function createSoundseems asyncronous... (It's like I/O Stuff, the most of the I/O operations in javascript -and the other languages- are async ).. Is it possible when you have called the console.log thisSound hasn't been fully created yet? Maybe you could emulate a setInterval() behaviour with a lot of time and this code works... (although I believe you should use a callback function for this).

Calling a function in javascript object literal notation declaration

I'm trying to call a function within an object literal that I created, using the this keyword. But an error shows up saying this.doTheMove() is not a function:
window.onload = function(){
var animBtn = document.getElementById('startAnim');
animBtn.addEventListener('click', Animation.init, false);
}
var Animation = {
init: function(){
this.doTheMove(); // I'm calling the function here, but it gives an error.
},
doTheMove: function(){
alert('Animation!');
}
}
Why is there an error?
An explanation of what's happening. Pointy's answer is good but I want to explain it more generically. A very good research on this can be found here
An event handler is just a callback. You pass it a function and an event to listen on. Interally all it will do is call that function.
Animation.init is just a getter for that function. Think of it like this:
var callback = Animation.init
animBtn.addEventListener('click', callback, false);
...
// internal browser event handler
handler() {
// internal handler does stuff
...
// Oh click event happened. Let's call that callback
callback();
}
So all you've done is passed in
var callback = function(){
this.doTheMove(); // I'm calling the function here, but it gives an error.
}
By default in javascript this === window. This will refer to the global object if it isn't set to something. The net effect is that window.doTheMove is called. And that function doesn't exist.
In this case since callback is actaully called by an event handler the this object points at the DOM object that triggered the event so your calling node.doTheMove which still doesn't exist.
What you wanted to do is wrap it with a reference to Animation.
var callback = function() {
Animation.init();
}
This is a function execution and it executes init on Animation. When you execute it on an object like that then internally this === Animation as you would expect.
To sum up. The issue here is that Animation.init is just a reference to a function. It has no information about anything else like Pointy mentioned.
You have to change the way you set that up:
window.onload = function(){
var animBtn = document.getElementById('startAnim');
animBtn.addEventListener('click', function() { Animation.init(); }, false);
}
In JavaScript, the fact that a function happens to be defined as part of an object literal really doesn't mean very much (if anything, in fact). The reference to Animation.init does get you to the proper function, but the problem is that when the function is later invoked (in response to an actual "click"), the browser calls the function but has no idea that the object "Animation" should be the this reference. Again, the fact that the function was declared as part of the object is of no importance at all here. Therefore, if you want this to be something in particular of your own choosing, then you have to make sure it's set explicitly in code you control. The solution above is about the simplest way to do it: it handles the "click" events with an anonymous function that does nothing other than invoke the "init" function via an explicit reference through "Animation". That will ensure that this refers to the "Animation" object when "init" runs.
Another alternative would be to use the ".bind()" facility that some browsers and frameworks support:
window.onload = function(){
var animBtn = document.getElementById('startAnim');
animBtn.addEventListener('click', Animation.init.bind(Animation); }, false);
}
The net effect is almost exactly the same: that call to ".bind()" returns a function that invokes the function on which it was called (that being the "init" function in the "Animation" object), and does so with its first argument as the this reference (the "context" object). That's the same thing that we get from the first example, or effectively the same anyway.
Here's another nice approach, I think.
window.onload = function(){
var animBtn = document.getElementById('startAnim');
animBtn.addEventListener('click', Animation.init, false);
};
var Animation = {
init: function(){
Animation.doTheMove(); // This will work, but your IDE may complain...
},
doTheMove: function(){
alert('Animation!');
}
};
You might want to use the portotype base approach:
// generate a prototype object which can be instantiated
var Animation = function() { this.doTheMove(); }
Animation.prototype.doTheMove = function() {
// if the object has only one method, the whole code could be moved to
// var Animation = function() {...} above
alert('Animation!');
}
Animation.prototype.otherMethod = function(param1, param2) {
// ...
}
// run the code onload
window.onload = function(){
var animBtn = document.getElementById('startAnim');
animBtn.addEventListener('click', new Animation(), false);
}
Six and a half years later, but I'm hoping my answer can also provide some insight for current and future developers.
I tend to code using literal objects inside of self defined functions, and the original question posted works just fine if another self-executing function is added along with a try and catch statement.
It's very important to point out that it's all about scope and context.
Please correct any drawbacks or provide more effective suggestions of using this method.
(function() {
console.log(this); // window object
var animation = {
init: function() {
this.doTheMove();
},
doTheMove: function() {
alert("Animation");
console.log(animation); // animation object
}
};
(function() {
try {
console.log("animation.init"); // animation.init function
animation.init();
} catch(e) {
console.log("Error is: " + e);
}
})();
})();

Categories

Resources