I've been going crazy over this and I think the answer is probably out there but I don't know the right way to ask Google the question.
Essentially I need a way to make a $resource call, and pass in some data that I want to then use in the success function.
app.controller('VariantListController', ['djResource', function(djResource){
var Variants = djResource('/ship-builder/variants/?format=json');
var Vehicle = djResource('/ship-builder/vehicles/:id', {id: '#id'});
this.variants = Variants.query(function(variants){
$(variants).each(function(){
console.log(this);
variantData = this;
var vehicleData = Vehicle.get({id:this.baseVehicle}, function(){
console.log(variantData);
})
})
});
}]);
In the above example, in the innermost success function, 'variantData' is always the value of the LAST entry from the previous level. This makes sense because the value was set by the last item in the array long before the success happens. I need a way though to have the value of the 'variantData' that was inexistince when the Vehicle.get() was called.
Does that make sense? I find it very hard to explain the issue.
You need to create a closure to make it work. Something like
this.variants = Variants.query(function(variants){
$(variants).each(function(){
getVehicleData(this);
})
});
function getVehicalData(variantData) {
var vehicleData = Vehicle.get({id:variantData.vehicleId}, function(){
console.log(variantData);
})
}
I am by no means an expert on the $resource service, but perhaps using the $promise.then method instead of the success callback would work.
$(variants).each(function(){
console.log(this);
variantData = this;
Vehicle.get({id:this.baseVehicle}).$promise.then(function() {
console.log(variantData);
});
});
Since the value in variantData may change before the success callback is actually called, you want to ensure the the callback has the original value stored.
var vehicleData = Vehicle.get({id:this.baseVehicle}, function(vData){
return function() {
console.log(vData);
}
}(variantData));
The above will create a new function with variantData stored in a closure.
Related
Here is the relevant snippet of code:
$scope.newLike = LikeFactory.newLike;
$scope.newDislike = LikeFactory.newDislike;
$scope.updateLike = LikeFactory.updateLike;
$scope.updateDislike = LikeFactory.updateDislike;
$scope.vote = function(sockId, nnew, update) {
if (!$scope.verifyUser) $scope.alert("Please log in to like a sock!");
if (!$scope.like.like && !$scope.like.dislike) {
return nnew(sockId).then(function(vote) { $scope.vote = vote; });
} else {
return update(sockId).then(function(update) { $scope.vote = update; });
}
}
I call this function, $scope.vote, in the html with an ng-click="vote(sock.id, newLike, updateLike)" or ng-click="vote(sock.id, newDisike, updateDislike)" whether on an like vs dislike button. The call button works fine when first liking and updating once there is an instance of a 'like' for a particular unique sock/user combo but after one 'update' I get the error:
angular.js:13642 TypeError: v2.vote is not a function
Do I need to trigger a $digest for the function to continue to be in $scope? Does it somehow come off $scope after being used? It seems like a strange error to me.
Edit: duh! It's late, thanks for the answers!
You define $scope.vote as a function in your controller. After first invocation you assign a result that may not be a function to this variable, thus vote is no longer a function:
$scope.vote = function() {} // => 'vote' variable is holding a function
$scope.vote = vote / update // => 'vote' might not reference a function but a value
Log your result after the promise is resolved (in the then block), to understand what is the new assigned value.
It's normal, here : $scope.vote = update you use the same varaible that the function name
The issue must be with the following statement:
$scope.vote = vote;
The vote might not be function and so v2.vote is not a function
I have a module with four functions that call one after the other. I am trying to follow the Revealing Module Pattern. One of the functions is public, the remaining are private. It goes like this:
publicMethod is called from another module
queryNames is called from publicMethod
execute(parameters, callback?, errback?) is called from queryNames
addNamesList is called as the callback? argument of execute
Several dijit/form/CheckBox's are created and the method querySegments is triggered onChange
querySegments needs to call a method of an object created in publicMethod.
The problem is in step 6, I can't reach the object created in step 1.
I have tried to use dojo hitch to define the callback? argument in step 3, but I can't get it to work. I tried putting this in its first argument, but even then I can't reach the required scope to call addNamesList.
Here is some code to demonstrate this issue.
define([
'dojo/dom',
'dijit/form/CheckBox',
'esri/layers/ArcGISDynamicMapServiceLayer',
'esri/tasks/query',
'esri/tasks/QueryTask',
'dojo/_base/lang'
],
function (
dom,
CheckBox,
ArcGISDynamicMapServiceLayer,
Query, QueryTask,
lang
) {
// ***************
// private methods
// ***************
// fetch names and call addNamesList to put the list in place
var queryNames = function (map, mapLayer) {
// new QueryTask(url, options?)
var queryTask = new QueryTask("url")
var query = new Query()
// execute(parameters, callback?, errback?)
// this callback passes an argument called featureSet
queryTask.execute(query, lang.hitch(map, "addNamesList", mapLayer), function(error) {console.log(error)})
}
// callback function of queryNames
var addNamesList = function (mapLayer, featureSet) {
console.log('addOplist')
var namesCount = featureSet.features.length
for (var i = 0; i <namesCount; i++) {
// work
var cbox = new CheckBox({
id: "cbox_" + i,
value: featureSet.features[i].attributes["someID"],
checked: false,
onChange: function (evt) {
querySegments(this.value, mapLayer)
}
})
cbox.placeAt("someDiv" + i, "first")
}
}
// triggered by the checkbox event
var querySegments = function (name, mapLayer) {
// build the query
var queryStatement = "someID = " + name
var layerDefinitions = [queryStatement]
// call a method of mapLayer
mapLayer.setLayerDefinitions(layerDefinitions)
}
// **************
// public methods
// **************
var publicMethod = function (map) {
var mapLayer = new ArcGISDynamicMapServiceLayer('restURL')
map.addLayer(mapServiceLayer)
queryNames(map, mapLayer)
return mapLayer
}
return {
publicMethod: publicMethod
}
}
)
You can see a more detailed explanation and a working example on this other (and more broad) question that I have put on Code Review.
I am new to JavaScript and I guess I still have a lot of issues with scoping, closures and callbacks.
I will deeply appreciate any input, including how to improve this question.
Edit
With this current implementation (with dojo hitch), no error is thrown. The method addNamesList is not called (nor errback, which I also don't understand why). I think this is because addNamesList is not on map's (hitch first argument) namespace. I tried to put this instead, but it makes no difference.
Before I decided to use hitch, the code looked like this:
var queryNames = function (map, mapLayer) {
...
queryTask.execute(query, addNamesList)
}
var addNamesList = function (featureSet) {
...
...
...
querySegments(this.value, mapLayer)
}
but then I couldn't reach mapLayer inside the method triggered by the check box event. It would throw Uncaught ReferenceError: mapLayer is not defined. That is why I tried to use hitch.
Javascript is asynchronous, so pretty much data coming from db, http requests or whatever is returned via callbacks. Here's what happens in your code:
public method calls queryNames
queryNames call addNamesList of map asynchronously and returns nothing
public method takes back control, meanwhile some stuff is going on with the addNamesList
mapLayer is returned untouched while some stuff is still going on in the background
So, to avoid this, you should return data from public method via callback, so you pass callback as the second parameter to the public method, then to the querySegments. Then, in the success callback of query, when you finally get the result ready, you do:
callback(mapLayer);
So, everything you should do is to pass this callback as deep as needed to the place where you have your mapLayer ready (so you've done with it everything you wanted), and then do a callback(mapLayer);.
This and this would probably explain better.
Best regards, Alexander
This may be a duplicate, and if so, I apologize. I've looked through a few questions and haven't found one that quite matches my situation (which maybe a bad sign to begin with).
I've got a class, say RandomClass, that is defined as follows
function RandomClass(id){
this._id = id;
}
RandomClass.prototype.getID = function(){
return this._id;
}
var rc = new RandomClass(1);
rc.getID(); //returns 1, as expected
Say I want to define a set of handlers, and keep them in a sub-object (while continuing to use prototype) of RandomClass. My knowledge of prototypes is somewhat limited, so apologies if this next bit is extremely bad form.
RandomClass.prototype.handlers = {};
RandomClass.prototype.handlers.HandlerOne = function(){
console.log("Handler one calling from ID: "+this._id);
//the context is not the context of RandomClass, but of RandomClass.prototype.handlers!
}
rc.handlers.HandlerOne(); //prints "Handler one calling from ID: unknown"
Again, maybe this is bad form, but I have several handlers which need to be called and doing things this way simplifies the code to:
var handler = "one of many many handlers returned from an ajax request";
rc.handlers[handler]();
So, my question is how do I make HandlerOne's context be the context of RandomClass rather than of handlers? I'd like to continue to use prototypes, because then they are not cloned multiple times (as in the following example):
function RandomClass(id){
this._id = id;
this._handlers = {};
}
function HandlerOne(){
console.log("Handler one calling from ID: "+this._id);
}
var rc = new RandomClass(1);
rc._handlers["HandlerOne"] = HandlerOne.bind(rc);
rc._handlers["HandlerOne"]() //prints as expected, but I believe performance is much worse here
Could satisfy to you do this, instead of bind the context try to pass it as a parameter.
function RandomClass(id){
this._id = id;
this._handlers = {};
}
function HandlerOne(instance){
var parentScope = instance;
console.log("Handler one calling from ID: "+parentScope._id);
}
//call it like this
var rc = new RandomClass(1);
rc._handlers["HandlerOne"] = HandlerOne;
rc._handlers["HandlerOne"](rc)
You could simply make Handlers it's own class. Note that you should not access private members from outside the class like I did in the exemple below. You must expose the correct public API to make objects work together without violating encapsulation.
function RandomClass(id){
this._id = id;
this.handlers = new Handlers(this);
}
function Handlers(randomClassInstance) {
this._randomClassInstance = randomClassInstance;
}
Handlers.prototype = {
constructor: Handlers,
handlerOne: function () {
console.log("Handler one calling from ID: "+ this._randomClassInstance._id);
}
};
Then you can do:
var rnd = new RandomClass('test');
rnd.handlers.handlerOne(); //Handler one calling from ID: test
Both answers submitted at this point are good alternatives (that I would say are acceptable), but I've decided to take another route (that lead to the least amount of modification to my code :)).
Similar to #BlaShadow's answer, rather than passing the context and setting a parentScope variable, I simply use Javascript's function.call() method to pass the correct context.
function RandomClass(id){
this._id = id;
}
function.prototype.handlers = {}
function.prototype.handlers.HandlerOne = function(data){
console.log("Handler one calling from ID: "+this._id+" with data: "+data);
}
var rc = new RandomClass(1);
rc.handlers.HandlerOne.call(rc, {"some": "data"});
//prints "Handler one calling from ID: 1 with data { "some" : "data" }
I have a simple angular $http.get, which returns a json object, but I want the id from the json to do another $http.get. I can do this by nesting another $http.get with the first one, but this seems rather stupid. What is the best way to assign the id of the response, data.id, to a variable? I'm having some issues with variable scope; trying to simply assign the value to a variable.
$http.get('/api/v1/foo/userinfo?thunk='+thunk+'&bar='+bar).success(function(data) {
$scope.id = data.id
}).then(function(data){
$scope.id = data.data.id
});
Why don't you watch your variable, in this case $scope.id like this:
$scope.$watch('id', function() {
// Http request goes here
$http.get(...)
});
When you assigned any value to $scope.id your "watch function" will be triggered.
Can use callbacks to make it cleaner. Wrap your call in a function:
function startAJAX(thunk, bar, callback) {
$http.get('/api/v1/foo/userinfo?thunk='+thunk+'&bar='+bar).success(function(data) {
callback(data.id);
});
}
Then make the call and do your next http call:
startAJAX(thunk, bar, function(id) {
$http(id).....
});
});
Using a watch() method isn't necessary. Use callbacks, or if you want to be fancy use promises to keep things organized.
var callOnePromise = $http.get('/api/foo');
var callTwoPromise;
callOnePromise.success(function(data) {
callTwoPromise = $http.get('/api/bar/' + data.id);
});
callTwoPromise.success(function() {
//Cool stuff here.
});
I have a JSON data structure:
[
{
"title" :"a1",
"id" :"b1",
"name" :"c1"
},
{
"title" :"a2",
"id" :"b2",
"name" :"c2"
}
]
I am accessing is as an external JSON and parsed through a factory method. I want it to assign it to a Javascript variable in my controller.
function Control($scope,data)
{
var e=data.query(); /* getting the external JSON data */
alert(e[0].title);
}
It says that e[0] is undefined. Is there any other way I can assign it to a Javascript variable and then traverse through it? Please help.
Most likely, #Marty is correct. If you are using the query() method from the $resource service, it is asynchronous. This will likely do what you want:
data.query( function( data ) {
var e = data;
alert(e[0].title);
});
Okay, so $resource can be confusing like this... It immediately gives you a reference to the return object, but doesn't update the object until the asynchronous AJAX call returns... so...
If you put your return value from data.query() in a property on $scope, since it's $watched when you bind it in your view, you'll see it update. HOWEVER, if you're just trying to alert it, it will alert the value before it's been updated.. again because of the async aspect of $resource.
Otherwise, you can get the value the way that #MarkRajcok has shown in his answer.
Here is a psuedo-code illustration of ways you can use $resource query();
app.controller('FooCtrl', function($scope, $resource) {
var Bar = $resource('/Bar/:id', {id: '#id'});
// here, we get the result reference and stick it in the scope,
// relying on a digest to update the value on our screen.
$scope.data = Bar.query();
//OR
//here we already have a reference.
var test = Bar.query(function() {
//in here, test has been populated.
$scope.data2 = test;
});
alert(test); //not populated here, yet.
//OR
Bar.query(function(x) {
$scope.data3 = x;
});
});
This is all done so the object(s) returned can have functions pre-instantiated on them like $save(), etc.