Asynchronous data retrieving and rendering in ExtJS with Ajax - javascript

so I have this situation:
renderer: function(value, grid, record) {
var testAjax = function(callback) {
Ext.Ajax.request({
url: appConfig.baseUrl + '/api/users/' + record.getData().id + '/jobRoles',
method: 'GET',
success: function(result) {
callback(result)
};
});
};
return testAjax(function(result) {
try {
result = JSON.parse(result.responseText);
} catch(e) {
return '';
}
result = result.data;
var roles = _.map(result, function(jRole) {
console.log(jRole);
return jRole.name;
}).join(',');
console.log("Roles: ", roles);
return roles;
});
}
What I wanted to achieve is that when I have to render a particular field, I make a call to my Loopback endpoint, retrieve some data about a relation, map it using a "," character and return the joined string in order to view it.
However, I think I have a few problem with callbacks here as I don't see the result at all, as if the function is returning before the callback is called (thus showing nothing instead of what I retrieved from the server).
I tried to look here and there, and this is the best I came up with.
How can I return to the parent function the "roles" variable? How do I properly set up my callbacks?
Regards

You cannot and should not use the renderer with load operations and asynchonous callbacks. The renderer can be called dozens of times for the same record, if you filter or sort or just refresh the grid view. What you want to do is get all the information required for display in the grid in a single call. Data you cannot get in the single call should not be shown in the grid. You don't want to call the endpoint 1000 times for 1000 records, because even if each call needs only 60ms, that's a full minute.
That said, if you really have to, because you cannot change the endpoints and the roles have to be displayed, you can do as follows:
dataIndex: 'MyTempRoles',
renderer: function(value, grid, record) {
if(value) return value; // show the loaded value if available
else { // no value loaded -> load value
Ext.Ajax.request({
url: appConfig.baseUrl + '/api/users/' + record.getData().id + '/jobRoles',
method: 'GET',
success: function(result) {
try {
result = JSON.parse(result.responseText);
result = result.data;
var roles = _.map(result, function(jRole) {
console.log(jRole);
return jRole.name;
}).join(',');
record.set("MyTempRoles", roles || " "); // put the loaded value into the record. This will cause a grid row refresh, thus a call to the renderer again.
} catch(e) {
}
}
});
}
}
This will call the backend in the first call to the renderer, and asynchronously fill the displayed record's temp variable. When the temp variable is filled, the renderer will then display the value from the temp variable automatically.

Related

Sending multiple GET requests with changing parameter in AngularJS

Edit: Apologies to the answerers, it turns out that this was actually valid code, but requests were being intercepted and stripped of all of the parameters.
I'm trying to make repeated HTTP GET requests to a REST API, depending on the output and have used the solution from this question.
However, I wish to increment one of the parameters that I pass in the request. Essentially, the API pages the output and I need to increase the value of startAt accordingly.
Manual requests work fine with:
<URL>/board?startAt=50
And give back:
{"maxResults":50,"startAt":50,"isLast":true,"values":[list_of_values]}
Here's my code so far:
function getHttpPromise(start_at) {
// This function recurses until the server returns isLast = true.
//
// Each iteration appends the values in response.values to
// $scope.boards.
test = $http({
url: 'boards',
method: 'GET',
params: {'startAt': start_at.toString()}
}).
success(function (response) {
console.log(response); // the response contains startAt, which always has
// the initial value (0), rather than start_at's value
var values = response.values;
for (var i in values) {
var board = values[i];
$scope.boards[board.id] = board;
}
if (response.isLast) {
// We have received all the boards.
return true;
} else {
// Increment start_at and return another http request promise.
start_at += response.maxResults;
return getHttpPromise(start_at);
}
}
);
console.log(test); // params is correct here
return test;
}
This function is called by:
jiraWorkLog.controller('SprintSelectCtlr',
function($scope, $http, $routeParams) {
$scope.init = function() {
$scope.boards = new Object();
getHttpPromise(0).then(
function (dummy_var) {
for (var board in $scope.boards) {
...
}
}
);
}
...
);
the response holds all the http stuff and you want to get the data out of it...
I think that the following link might help you Angular Http
another issue that you have with your recursion is that you're updating a local variable and use it wrongly...
function getHttpPromise(start_at) {
// This function recurses until the server returns isLast = true.
//
// Each iteration appends the values in response.values to
// $scope.boards.
test = $http({
url: 'boards',
method: 'GET',
params: {'startAt': start_at.toString()}
}).
success(function (response) {
console.log(response.data); // the response contains startAt, which always has
// the initial value (0), rather than start_at's value
var values = response.data;
for (var i in values) {
var board = values[i];
$scope.boards[board.id] = board;
}
if (response.data.isLast) {
// We have received all the boards.
return true;
} else {
// Increment start_at and return another http request promise.
return getHttpPromise(response.data.maxResults+1);
}
}
);
console.log(test); // params is correct here
return test;
}
The $http().success() is deprecated. You should use $http().then().
The then receives a function than will be passed a response object that has a property called data. There you'll find your values.
You can read further here
After reading your code more thoroughly, I must advise you not to solve this problem recursively. If you don't want the data paged, send a request for all the records upfront.
Now to answer why you only get the first result: it's because that's the only thing that is returned and the calling controller. You are returning a promise that the controller is waiting to be resolved. All other recursive calls happen only after your controller is already done.

Function Keep Triggering Before JSON Done Processing

Function 1: Get JSON Data & Store
I am creating a script where an array of twitch channels will go through the JSON function loop to be processed and then stored using "localStorage.setItem" as temporary storage. I'm saving them in name,viewer and url.
Function 2: Sort Data
Stored data can later be used to display the information without having to use function 1 again.
Problem
The sortdata function keeps on firing before function 1 is complete. Resorting in error because the data is undefined. This error popped before the console displays all the information stored from function 1.
My code:
$(window).load(function(){
$.when(getData()).promise().done(function(){
getStoredObj();
});
});
function getData(){
var streamArray=[];
jQuery.each (channels, function (i, channel) {
channelId = channel.id;
channelUrl = channel.url;
var promise = $.ajax({
dataType: "jsonp",
url: twitchApi + channelId,
success: 1,
}).done(function ( data ) {
if (data.stream == null) {
} else {
var displayName = data.stream.channel.display_name;
var viewerCount = data.stream.viewers;
streamArray.push({name: displayName, views: viewerCount, url: channelUrl});
localStorage.setItem("storedStreamArray", JSON.stringify(streamArray));
console.log(JSON.stringify(streamArray));
}
});
});
}
function getStoredObj () {
var retrievedObject = localStorage.getItem('storedStreamArray');
var parsedObject = JSON.parse(retrievedObject);
<sorting codes here>
}
Some help here really appreciated. :)
You're calling $.when with the result of getData, but getData doesn't return anything, let alone a deferred that when can use. As a result, there's nothing to wait for and your done callback calls getStoredObj immediately.
In getData, you need to collect all the deferreds returned by your ajax calls and pass them back to the caller. That would look like:
function getData(){
return jQuery.map (channels, function (i, channel) {
return $.ajax(...).done(function ( data ) {
// Do work
});
});
}
Each iteration returns its ajax deferred, which are aggregated by map and returned to the caller. Then you can run when on the result and wait for loading to finish before you sort anything.

Azure mobile service - get row(s) based on matching string/substring value

I am trying to read results from JavaScript azure mobile service and I want to return items with a specific values. I am using getTable(‘’).where.(‘’).read(‘’) to check if one of the returned json value match a specific pattern as shown in this post:
Here is my client side script:
function getSP() {
var spUser = client.getTable("User").where(function (contains) {
return this.extraname.indexOf(contains) > 0;
}, "Eyad").read().done(function (results) {
alert(JSON.stringify(results));
}, function (err) {
alert("Error: " + err);
});}
And the request URL generated from client:
https://XYZ.azure-mobile.net/tables/User?$filter=(indexof(extraname,'Eyad') gt 0)
But the code above return an empty [] object form the service, while performing the same operation without the where() check clearly returns my intended value:
What am I doing wrong? How can I return the row(s) where the returned "extraname" contains the substring "Eyad"?
NOTE: I also have a custom read script on the service side, and you can see the "extraname" value is hardcoded for testing purposes:
function read(query, user, request) {
request.execute({
success: function(results) {
var now = new Date();
results.forEach(function(item) {
console.log(item.userjsondata);
item.extraname = "Eyad";
});
request.respond(); //Writes the response
}
});
}
If you're generating a new column dynamically, then there's no way to - directly - use a $filter clause to filter the results based on that value. Notice that you can write arbitrary code to calculate that value, so the mobile service runtime has no way to know what value it will end up generating, and cannot perform a filter based on that.
There are a couple of workarounds for your solution: if possible, you can send a where clause with the same expression that you use to generate the value. In some cases the service will accept that expressions in the $filter clause as well. That has the drawback that you'll end up with the same logic in two different places, and there's a big chance that you'll change one and end up forgetting to change the other.
Another alternative is to pass the parameter for which you want to query the generated property not in the $filter parameter (i.e., don't use the where function, but pass the parameters inside the read call. Those parameters will be passed to the read script in the request.parameters object, and you can add your logic to filter based on that value after you're done reading from the database (see below).
Client:
var spUser = client.getTable("User").read({mustContain: "Eyad").done(function (results) {
alert(JSON.stringify(results));
}, function (err) {
alert("Error: " + err);
});}
Service:
function read(query, user, request) {
var mustContain = request.parameters.mustContain;
request.execute({
success: function(results) {
var now = new Date();
var filteredResults = [];
results.forEach(function(item) {
console.log(item.userjsondata);
item.extraname = "Eyad";
if (item.extraname.indexOf(mustContain) >= 0) {
filteredResults.push(item);
}
});
request.respond(200, filteredResults); //Writes the response
}
});
}
change your comparison from this:
return this.extraname.indexOf(contains) > 0;
to this:
return this.extraname.indexOf(contains) >= 0;
A matched string can be (in your case, always) found in the first index. Therefore you have to use greater than OR equal to operator to handle that case.

Ember Data model find by query deferred?

If I have a query that defines the model for my route:
App.MyRoute = Ember.Route.extend({
model: function (params) {
return this.store.find('MyModel', {myProperty: myValue}).then(function(results) {
return results.get('firstObject');
});
}
});
How do I handle the case where the store has not yet been populated with the results of the above query?
I want the query to update the model with the results of the above query whenever a value is inserted into the store that satisfies the query.
Is this possible?
NOTE: I'm using the FixtureAdapter as I'm not using a REST back end and I don't need the persistence layer. As such, I have implemented the findQuery method myself as follows:
App.ApplicationAdapter = DS.FixtureAdapter.extend({
queryFixtures: function(records, query, type) {
return records.filter(function(record) {
for(var key in query) {
if (!query.hasOwnProperty(key)) { continue; }
var value = query[key];
if (record[key] != value) { return false; }
}
return true;
});
}
});
I am absolutely open to changing this up if it will help me reach this end.
Use a filter, it is a live record array that updates as new records are injected into the store. As a side note, filter doesn't make a call to the server for records, so if you still want that to happen you'll want to still make the find call, and then just return the filter.
App.MyRoute = Ember.Route.extend({
model: function (params) {
// trigger a find
this.store.find('MyModel', {myProperty: myValue});
// return a live filter (updates when the store updates)
return this.store.filter('MyModel', function(record){
return record.get('myProperty') == myValue;
});
}
});

Return an object from a function that has nested ajax calls

I would like to write a javascript function that returns informations from youtube videos; to be more specific I would like to get the ID and the length of videos got by a search, in a json object. So I took a look at the youtube API and I came out with this solution:
function getYoutubeDurationMap( query ){
var youtubeSearchReq = "https://gdata.youtube.com/feeds/api/videos?q="+ query +
"&max-results=20&duration=long&category=film&alt=json&v=2";
var youtubeMap = [];
$.getJSON(youtubeSearchReq, function(youtubeResult){
var youtubeVideoDetailReq = "https://gdata.youtube.com/feeds/api/videos/";
for(var i =0;i<youtubeResult.feed.entry.length;i++){
var youtubeVideoId = youtubeResult.feed.entry[i].id.$t.substring(27);
$.getJSON(youtubeVideoDetailReq + youtubeVideoId + "?alt=json&v=2",function(videoDetails){
youtubeMap.push({id: videoDetails.entry.id.$t.substring(27),runtime: videoDetails.entry.media$group.media$content[0].duration});
});
}
});
return youtubeMap;
}
The logic is ok, but as many of you have already understood because of ajax when I call this function I get an empty array. Is there anyway to get the complete object? Should I use a Deferred object? Thanks for your answers.
Yes, you should use deferred objects.
The simplest approach here is to create an array into which you can store the jqXHR result of your inner $.getJSON() calls.
var def = [];
for (var i = 0; ...) {
def[i] = $.getJSON(...).done(function(videoDetails) {
... // extract and store in youtubeMap
});
}
and then at the end of the whole function, use $.when to create a new promise that will be resolved only when all of the inner calls have finished:
return $.when.apply($, def).then(function() {
return youtubeMap;
});
and then use .done to handle the result from your function:
getYoutubeDurationMap(query).done(function(map) {
// map contains your results
});
See http://jsfiddle.net/alnitak/8XQ4H/ for a demonstration using this YouTube API of how deferred objects allow you to completely separate the AJAX calls from the subsequent data processing for your "duration search".
The code is a little long, but reproduced here too. However whilst the code is longer than you might expect note that the generic functions herein are now reusable for any calls you might want to make to the YouTube API.
// generic search - some of the fields could be parameterised
function youtubeSearch(query) {
var url = 'https://gdata.youtube.com/feeds/api/videos';
return $.getJSON(url, {
q: query,
'max-results': 20,
duration: 'long', category: 'film', // parameters?
alt: 'json', v: 2
});
}
// get details for one YouTube vid
function youtubeDetails(id) {
var url = 'https://gdata.youtube.com/feeds/api/videos/' + id;
return $.getJSON(url, {
alt: 'json', v: 2
});
}
// get the details for *all* the vids returned by a search
function youtubeResultDetails(result) {
var details = [];
var def = result.feed.entry.map(function(entry, i) {
var id = entry.id.$t.substring(27);
return youtubeDetails(id).done(function(data) {
details[i] = data;
});
});
return $.when.apply($, def).then(function() {
return details;
});
}
// use deferred composition to do a search and then get all details
function youtubeSearchDetails(query) {
return youtubeSearch(query).then(youtubeResultDetails);
}
// this code (and _only_ this code) specific to your requirement to
// return an array of {id, duration}
function youtubeDetailsToDurationMap(details) {
return details.map(function(detail) {
return {
id: detail.entry.id.$t.substring(27),
duration: detail.entry.media$group.media$content[0].duration
}
});
}
// and calling it all together
youtubeSearchDetails("after earth").then(youtubeDetailsToDurationMap).done(function(map) {
// use map[i].id and .duration
});
As you have discovered, you can't return youtubeMap directly as it's not yet populated at the point of return. But you can return a Promise of a fully populated youtubeMap, which can be acted on with eg .done(), .fail() or .then().
function getYoutubeDurationMap(query) {
var youtubeSearchReq = "https://gdata.youtube.com/feeds/api/videos?q=" + query + "&max-results=20&duration=long&category=film&alt=json&v=2";
var youtubeVideoDetailReq = "https://gdata.youtube.com/feeds/api/videos/";
var youtubeMap = [];
var dfrd = $.Deferred();
var p = $.getJSON(youtubeSearchReq).done(function(youtubeResult) {
$.each(youtubeResult.feed.entry, function(i, entry) {
var youtubeVideoId = entry.id.$t.substring(27);
//Build a .then() chain to perform sequential queries
p = p.then(function() {
return $.getJSON(youtubeVideoDetailReq + youtubeVideoId + "?alt=json&v=2").done(function(videoDetails) {
youtubeMap.push({
id: videoDetails.entry.id.$t.substring(27),
runtime: videoDetails.entry.media$group.media$content[0].duration
});
});
});
});
//Add a terminal .then() to resolve dfrd when all video queries are complete.
p.then(function() {
dfrd.resolve(query, youtubeMap);
});
});
return dfrd.promise();
}
And the call to getYoutubeDurationMap() would be of the following form :
getYoutubeDurationMap("....").done(function(query, map) {
alert("Query: " + query + "\nYouTube videos found: " + map.length);
});
Notes:
In practice, you would probably loop through map and display the .id and .runtime data.
Sequential queries is preferable to parallel queries as sequential is kinder to both client and server, and more likely to succeed.
Another valid approach would be to return an array of separate promises (one per video) and to respond to completion with $.when.apply(..), however the required data would be more awkward to extract.

Categories

Resources