Returning data from JSONP callback - extjs

I have a class that fetches jsonp data. I would like to simply return a value but am unable to do so. The callback does not update the class property - not sure if it is a timing or scope issue
Ext.define("App.ContentManager", {
extend: "Ext.util.Observable",
singleton: true,
data: 'empty',
getData: function() {
this.doLoad();
console.log(a.data) //not correct - shows original value
return this.data;
},
doLoad: function(url) {
var a = this;
Ext.util.JSONP.request({
url: "http://example.com/somejson",
callbackKey: "callback",
callback: function(c) {
a.data = c;
console.log(a.data) //correct json data is here
}
})
},
});

Of course is timing issue. With doLoad() in the getDate function you just start the request and you don't wait for it to complete in order to have the data ready in the next line. You only have the data ready in the callback: function of the doLoad method. So whatever you need to do it's best to send the request in one function and then in the callback:function have a function that will use the data returned.
Update
Also I just noticed is a scope issue too. You shout pass the scope in the JSONP request with adding a
url: "http://example.com/somejson",
scope: this, //this line
callbackKey: "callback",
And then in the getDate you should: this.data since a is local variable in the doLoad method.

As noted in existing comments, since the doLoad method retrieves the data asynchronously, there is no way to return the result directly from the method call. Your two options would be:
Define your method with a callback parameter (this is done commonly in Ext itself) so that inside the JSONP callback you would execute the passed callback function, passing the returned data.
Define a custom event that your class could fire from the JSONP callback, passing the data as an event argument. This would be more suitable in the case where multiple components might be interested in the result of the JSONP call.
Or you could even do both (untested):
doLoad: function(options) {
var me = this;
Ext.util.JSONP.request({
url: options.url,
scope: me,
callbackKey: "callback",
callback: function(data) {
me.fireEvent('dataloaded', me, data);
if (options.callback) {
options.callback.apply(options.scope || me, [data]);
}
}
})
}

Related

I'm having problems accessing a $resource variable in AngularJS

I made a request for a php getting a JSON string, when I access the string in my template using $ctrl.respostas[0].status it returns a string with a value, but when I try to use the same model in my controller (this.respostas[0].status), it came nothing, I need to check the answer in my controller, but I don't know how, any one can help me?
Template:
<p>respostas: {{$ctrl.response}}</p>
<p>confere: {{$ctrl.test}}</p>
<p>status: {{$ctrl.response[0].status}}</p>
Controller
angular.
module('tela').
component('tela', {
templateUrl: 'tela/tela.template.html',
controller: ['Request',
function telaController(Request, ̶$̶r̶o̶o̶t̶S̶c̶o̶p̶e̶) {
this.test = "ola";
this.id = "1";
this.validate = function validate() {
this.response= Request.save({id: this.id},[]);
this.test = "algo";
this.test = this.response[0].status;
}
}
]
});
Request
angular.
module('request.Request').
factory('Request', ['$resource',
function($resource) {
return $resource('./php/acessing.php',{
teste: "#id",
}, {
save: {
method: 'POST',
hasBody: true,
isArray: true,
cache: false
}
});
}
]);
It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data.
The HTML template shows the data because the interpolation bindings {{ }} check the object every digest cycle and update the DOM. The controller on the other hand needs to use the attached promise to examine the data:
app.component('tela', {
templateUrl: 'tela/tela.template.html',
controller: ['Request',
function telaController(Request, ̶$̶r̶o̶o̶t̶S̶c̶o̶p̶e̶) {
this.test = "ola";
this.id = "1";
this.validate = function validate() {
this.response= Request.save({id: this.id},[]);
var promise = this.response.$promise;
promise.then(data => {
console.log(data[0].status);
console.log(this.response[0].status);
});
}
}
]
});
From the Docs:
The Resource instances and collections have these additional properties:
$promise: The promise of the original server interaction that created this instance or collection.
On success, the promise is resolved with the same resource instance or collection object, updated with data from server.
On failure, the promise is rejected with the http response object.
— AngularJS $resource API Reference - Usage

AngularJs: Does not update the model after the modifying it through $http transform

I need to transform objects coming from $http call to an api. My code adds some fields (functions) to the object coming from the api, here the constructor of this object :
(function () {
window.TransformedObject = function (obj) {
var self = this;
self = {};
if (obj) {
self = angular.copy(obj);
}
self.hasChanged = function () {
// return true or false if the object has changed
}
return self;
}
}());
The $http transform code looks like this :
$http({
url: 'api/...',
method: 'GET',
transformResponse: function(value) {
return new TransformedObject(JSON.parse(value));
})
}).success(function(data){
vm.obj = angular.copy(data);
});
Note that the value in the transformResponse callback is stringified, and need to be parsed to get the object
All this is working fine, suppose the object coming from the api contains a key called title, doing obj.title = 'some title' will update the object.
The problem :
Binding the title field with an input tag will not update the object if the change is coming from the view.
I use a regular ng-model to do it:
<input type="text" placeholder="Title" ng-model="vm.obj.title"/>
even using $rootScope.$watch will never be triggered if the change is coming from the view aka the input tag.
$rootScope.$watch(function () {
return vm.obj;
}, function () {
console.log('watch');
// this log will never appear in the console
});
Am I doing something wrong, why transforming the object coming from the api is breaking angulars binding ???
Thanks.
http://www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm
Sometimes, in an AngularJS application, you have to explicitly tell
AngularJS when to initiate it's $digest() lifecycle (for dirty-data
checking). This requirement is typically contained within a Directive;
but, it may also be in an asynchronous Service. Most of the time, this
can be easily accomplished with the $scope.$apply() method. However,
some of the time, you have to defer the $apply() invocation because it
may or may not conflict with an already-running $digest phase. In
those cases, you can use the $timeout() service; but, I'm starting to
think that the $scope.$evalAsync() method is a better option.
...
Up until now, my approach to deferred-$digest-invocation was to
replace the $scope.$apply() call with the $timeout() service (which
implicitly calls $apply() after a delay). But, yesterday, I discovered
the $scope.$evalAsync() method. Both of these accomplish the same
thing - they defer expression-evaluation until a later point in time.
But, the $scope.$evalAsync() is likely to execute in the same tick of
the JavaScript event loop.

Custom caching for $resource

I have the following pattern in my AngularJS which calls for refactoring:
$scope.advertisers = Advertiser.query()
$scope.advertisersMap = {};
$scope.advertiser.$then(function (response) {
arrayToMap(response.resource, $scope.advertisersMap)
}
arrayToMap is function that adds each item in an array to the object with it's ID as key.
Now, I would have liked that this will happen in Advertiser itself, e.g.
$scope.allAdvertisers = Advertiser.query()
// Somewhere else
var advertiser = Advertiser.get({id: 2})
Where Advertiser.get({id: 2}) will return from a cache populated earlier by the query method.
Advertiser is defined in factory:
.factory('Advertiser', ['djResource', 'Group', function ($djResource, Group) {
var Advertiser = $djResource('/campaigns/advertisers/:advertiserId/', {advertiserId: '#id'});
Advertiser.prototype.getGroups = function () {
return Group.getByAdvertiser({advertiserId: this.id});
};
return Advertiser;
}])
Sadly, DjangoRestResource (and $resource which it wraps) caches by URLs, so query() will cache /advertisers/ while get(2) will cache /advertisers/2/, but I want query to cache in such way that get will be able to retrieve it as well.
I've tried replacing the query function by a wrapper function that does the caching, but I want it to return an promise which is also an Array like $resource does. It was something like:
var oldQuery = Advertiser.query;
var cache = $cacheFactory('advertisers');
Advertiser.query = function () {
var promise = oldQuery();
return promise.then(function (response) {
angular.forEach(response.resource, function (resource) {
cache.put(resource.id, resource)
})
})
};
But then the returned promise is no longer an Array-like object, and it doesn't not encapsulate the returned results in an Advertiser object as it used to, which breaks most of my code expects that Advertiser.query() will eventually be an Array.
What other approaches should I try? This snippet repeats itself in every controller for multiple factories and it hurts my eyes.
Here is solution to the problem:
function makeCachingMethod(object, method, cache, config) {
var $q = angular.injector(['services']).get('$q');
var oldMethod = object[method];
object[method] = function () {
var result;
if (!config.isArray) {
var id = config.idParam ? arguments[0][config.idParam] : arguments[0];
result = cache.get(id);
if (result !== undefined) {
if (result.$promise === undefined) {
result.$promise = $q.when(result);
result.$resolved = true;
}
return result;
}
}
result = oldMethod.apply(this, arguments);
result.$promise.then(function (data) {
if (config.isArray) {
angular.forEach(data, function (item) {
cache.put(item.id, item);
})
} else {
cache.put(data.id, data);
}
return data;
});
return result;
}
}
And an example usage:
app.factory('Country', ['$resource', '$cacheFactory', function ($resource, $cacheFactory) {
var Country = $resource('/campaigns/countries/:id', {id: "#id"}, {
query: {method: 'GET', url: '/campaigns/countries/', isArray: true},
get: {method: 'GET', url: '/campaigns/countries/:id/', isArray: false}
});
var cache = $cacheFactory('countries');
makeCachingMethod(Country, 'query', cache, {isArray: true});
makeCachingMethod(Country, 'get', cache, {idParam: 'id'});
return Country;
}])
What is happening here?
I use makeCachingMethod to decorate the original method created by $resource. Following the pattern used by $resource itself, I use a configuration object to signal whether the decorated method returns an array or not, on how the id is passed in queries. I assume, though, that the key of the ID to save is 'id', which is correct for my models but might need to be changed.
Noticed that before returning an object from the cache, the decorator adds to it $promise and $resolved attributes, since my application expects objects originated from $resource which have these properties, and in order to keep using the promises API, e.g.:
$scope.advertiser = Advertiser.get({advertiserId: $scope.advertiserId});
$scope.advertiser.$promise.then(function () {
$scope.doSomething();
});
Notice that since the function is defined outside the scope of any Angular module it is required to inject the $q service using angular.injector(). A nicer solution will be to return a service for invoking the decorator function. Such service could also handle the generation of the caches themselves.
This solution does not handle the expiration of cached models, which isn't much of problem in my scenario, as these rarely change.
I'm not aware of any cache support directly with $resource, you could create a factory that abstracts the Advertiser resource and handle the caching yourself.
Here is some helpful info on caching:
https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#validating-cached-responses-with-etags
I'm working on an app at the minute using $resource and every method is abstracting so I can check the cache to see if what I'm requesting is there and return it, if not, make the call using $resource.

add a custom method to a backbone model calling server, returning a simple data type

Looking for some guidance on properly implementing custom logic on a model ("foo") using backbone.
We have a general requirement to avoid invocation direct .ajax calls (see below), and try to do everything consistently through the Backbone.js MV* framework.
I have implemented a method on the view (for lack of better place to put it), but I feel like this utility method would better be suited as a method on the model.
I've read articles suggesting implementation of over-riding the backbone .sync method to add custom methods on a model, but the trouble is, that I'm not sure it's "correct" to do so, when the model is expecting an object, the REST service method I'm actually invoking returns a simple boolean (true/false) value.
Here is the code I have implemented:
var myView = Backbone.View.extend({
initialize: function (options) {
...
},
canDelete: function (parmOne, parmTWo){
var okToDelete;
var url = '/url/to/check/if/delete/is/ok/parmOne/ParmTwo';
$.ajax({
async: false,
type: "GET",
url: url,
success: function (valBool, response, jqXHR) {
okToDelete = valBool;
},
error: function (data, textStatus, jqXHR) {
notifyError(data.responseText)
}
});
return okToDelete;
}
});
Looking for suggestions on implementation to make this cleaner?
Thanks
You are right about not overwriting Backbone.sync. As per the docs:
By default, it uses jQuery.ajax to make a RESTful JSON request and returns a jqXHR. You can override it in order to use a different persistence strategy, such as WebSockets, XML transport, or Local Storage.
You'd override sync if you want to define a different peristence strategy.
Model-specific utility functions belong in the model
For a utility function like yours the correct place to implement it would be on the model.
var myModel = Backbone.Model.extend({
canDelete: function (parmOne, parmTWo){
var url = '/url/to/check/if/delete/is/ok/parmOne/ParmTwo';
return $.ajax({
async: false,
type: "GET",
url: url
});
}
});
In your view, you'd probably have an even bound to a delete function, say, deleteModel. Since we wired the model.canDelete method to return the $.ajax promise we can do the error checking in the view, like this:
var myView = = Backbone.View.extend({
initialize: function (options) {
...
},
deleteModel: function(event) {
var that = this;
this.model.canDelete()
.done(function (data) {
// Do some stuff if ok to delete like:
that.model.destroy();
})
.fail(function (data) {
// Do some stuff if not ok to delete like:
that.model.notifyError(data.responseText)
});
}
});
Of course you can keep your success/fail callbacks like you had them, if the model is only going to be used in a very limited manner.

When is $scope.$apply necessary for dealing with objects/arrays with Angular?

I'm working with the Soundcloud JS SDK to bring my Soundcloud favorites into a simple Angular app.
I wasn't able to get the user favorites to import in correctly until I used $scope.$apply.
function TopListCtrl($scope, $http, $modal) {
$scope.getData = function(sc_user) {
SC.get('/users/'+ sc_user +'/favorites', {limit: 200}, function(tracks){
$scope.$apply(function() {
if (Object.getOwnPropertyNames(tracks).length > 1) {
$scope.likes = tracks;
$scope.sortField = 'like.favoritings_count';
$scope.reverse = true;
$scope.sc_user = sc_user;
}
else {
alert("That user has 0 Soundcloud likes. So sad...")
}
}).error(function (data, status, headers, config) {
alert("Something went awry with that request. Double check that's a real Soundcloud username");
})
});
}
If you don't use $scope.apply, it doesn't work (and says SC.get not defined).
I'd like to understand a bit better why $scope.$apply is necessary. I ask this because when I was just using the http api, I didn't need it.
function TopListCtrl($scope, $http, $modal) {
$scope.getData = function(sc_user) {
var url = 'http://api.soundcloud.com/users/'+ sc_user +'/favorites.json?client_id=0553ef1b721e4783feda4f4fe6611d04&limit=200&linked_partitioning=1&callback=JSON_CALLBACK';
$http.jsonp(url).success(function(data) {
if (Object.keys(data.collection).length > 0) {
$scope.likes = data;
$scope.sortField = 'like.favoritings_count';
$scope.reverse = true;
$scope.sc_user = sc_user;
}
else {
alert("That user has 0 Soundcloud likes. So sad...")
}
}).error(function (data, status, headers, config) {
alert("Something went awry with that request. Double check that's a real Soundcloud username");
});
}
Usually angular knows about the code that's executing because you're the one providing the function callbacks but it's angular that's actually calling them. After angular calls a function, it will call $apply sometime later to trigger a $digest cycle.
If you don't know what a $digest cycle is, the concept is simple. During the $digest phase, angular will do a dirty check on every scope variable that's been set up with a $watch handler and check if it's changed; if it has angular will call its the corresponding $watch handler to update the view.
Getting back to the original question - when angular knows about your code, it will trigger a $digest cycle for you - so there is no need to call $apply explicitly. If you handle a jquery event, that's a different story. Angular has no idea that a $digest might be needed - how can it? So $apply is needed to trigger the $digest manually.
I know that you've already received the correct response to your question. I thought I'd also mention that it's not terribly difficult to use $http to make requests to the Soundcloud API, so that you won't need to use $scope.$apply. Here are mine:
var request = function(method, path, params, callback) {
params.client_id = sc.soundcloud.client_id;
params.oauth_token = sc.soundcloud.access_token;
$http({
method: method,
url: sc.soundcloud.api.host + path,
params: params
})
.success(callback);
};
get: function(path, params, callback) {
request('GET', path, params, callback);
},
put: function(path, params, callback) {
request('PUT', path, params, callback);
},
post: function(path, params, callback) {
request('POST', path, params, callback);
},
delete: function(path, params, callback) {
request('DELETE', path, params, callback);
}
Pixelbits' answer and an article by Jim Hoskins on $scope.$apply helped me understand this a little better. Here's the crucial point per my original question:
So, when do you need to call $apply()? Very rarely, actually.
AngularJS actually calls almost all of your code within an $apply
call. Events like ng-click, controller initialization, $http callbacks
are all wrapped in $scope.$apply(). So you don’t need to call it
yourself, in fact you can’t. Calling $apply inside $apply will throw
an error.
You do need to use it if you are going to run code in a new turn. And
only if that turn isn’t being created from a method in the AngularJS
library. Inside that new turn, you should wrap your code in
$scope.$apply()
(emphasis mine)
I'm still hazy on turns but I get the crucial point is that the method (SC.get in my case) isn't part of the AngularJS library so I, therefore, need to use $apply.
(At least I think I get it)

Resources