How to Execute ajax call after the scroller reaches the bottom - javascript

I'm correcting working on lazy loading for 200 array of objects and APIs are provided to me to extract JSON from the server (by passing index, row count as parameter for the get AJAX and on response I get the data and the Boolean of whether there are more rows or not). But the problem is that initially I was able to get the data of 10 from the 200 but while I set the scroll function on the div it displays duplicate data which are already appended on the div. Stucked in this problem for a day.
Hope you guys shed some light on me.
var listgen = (function() {
var mc = {};
mc.startindex = 1;
mc.rowcount = 10;
mc.hasmorerows = false;
mc.entity = "requests"
//Declared variables:
mc.initComponent = function() {
var entity = "requests";
mc.callAjaxForList(mc.entity, mc.startindex, mc.rowcount);
$("#reqbody").on('scroll', function() {
if (mc.hasmorerows && ($(this)[0].scrollHeight <= $(this).scrollTop() + $(this).innerHeight())) {
console.log('reached')
mc.callAjaxForList(mc.entity, mc.startindex, mc.rowcount);
}
console.log("scroll");
})
}
mc.callAjaxForList = function(entity, startindex, rowcount) {
var options = {
"list_info": {
"row_count": rowcount,
"start_index": startindex
}
}
$("#reqbody").addClass("loading");
$.ajax({
url: "/data/" + entity,
data: {
"input_data": JSON.stringify(options)
},
contentType: "application/json; charset=utf8",
type: "GET",
success: function(json) {
mc.hasmorerows = json.list_info.has_more_rows
mc.onDataLoading(json);
},
});
}
mc.onDataLoading = function(json) {
//this is where i append the data from the json
mc.startindex += mc.rowcount
}
return mc;
})()
listgen.initComponent();

Scroll is a very high-frequent event, so I think that you have several ajax calls with same data before actually your onDataLoading called, and range incremented. So I whould add mutex.
// ...
mc.loaging = false; // mutex
$("#reqbody").on('scroll', function(){
if(mc.hasmorerows && ($(this)[0].scrollHeight<=$(this).scrollTop()+$(this).innerHeight())){
console.log('reached')
if (!mc.loading) // add check here
mc.callAjaxForList(mc.entity,mc.startindex,mc.rowcount);
}
console.log("scroll");
})
}
mc.callAjaxForList= function(entity,startindex,rowcount){
// ...
mc.loading = true;
$.ajax({
// ...
success:function(json){
mc.hasmorerows=json.list_info.has_more_rows
mc.onDataLoading(json) ;
mc.loading = false;
},
error: ()=> mc.loading = false
});
}
So our mc.loading will tell us if ajax already completed (do not forget to reset it's value on ajax error)

Related

How to pass an ajax result to a Public veriable in the same class?

I have a jQuery file which also uses unserscore.js. It controls the selections of dates and different venues. For one of the pages it also controls which visuals are displayed depending on the type of venue. I can successfully, using ajax, get the type of page, but I have been unable to pass that value to a public variable in the script. It is based on which WiFi spot the data is coming from. If the data is from a local spot the page should display a d3 bubble chart. If it's from a remote spot it should display a map of the venue. Currently I have the functionality working with hard coding based on the id of the venue which is far from ideal.In order to make the decision based on which spot the venue is using I created an ajax call that gets the "spot". With console.log I can see that I am getting the correct result from the ajax call, but I'm missing something in terms of passing that information to a variable so I can use it.
This is the complete jQuery files:
define([
"ui/selects",
], function (SelectsUiClass) {
var global = this;
var MainControlsClass = function () {
// Private vars
var _this = this,
_xhr = null,
_selects = new SelectsUiClass(),
_dateRangeSelect,
_venueSelect,
_floorSelect,
_zoneSelect;
// Public vars
this.Selects = null;
this.spotName = null;
// Private Methods
var _construct = function () {
_dateRangeSelect = _selects.InitSelect('#mainControls-dateRange', _onSelectChange);
_venueSelect = _selects.InitSelect('#mainControls-venue', _onSelectChange);
_floorSelect = _selects.InitSelect('#mainControls-floor', _onSelectChange);
_zoneSelect = _selects.InitSelect('#mainControls-zone', _onSelectChange);
var value = _this.GetVenue();
_getChartDisplayDiv(value);
};
var _getChartDisplayDiv = function (venueId) {
var path = window.location.pathname,
pathArray = path.split("/"),
page = pathArray[pathArray.length - 1];
console.log('controlsjs 36, navigation page: ' , page);
console.log('controlsjs 37, venue value: ' , venueId);
_this.Load(venueId);
console.log('Controls 40, sPot Name = ', _this.spotName);
if (page === 'heatmap') {
if (venueId === 8 || venueId === 354) {
//make the bubble div visible
$("#heatmap-bubble").show();
//make the map div invisible
$("#heatmap-map").hide();
} else {
//make the map div visible
$("#heatmap-map").show();
//make the bubble div invisible
$("#heatmap-bubble").hide();
}
}
}
this.Load = function (venueId) {
console.log("Controls 66, Venue Id sent = ", venueId);
if (_xhr) {
_xhr.abort();
_xhr = null;
}
_this.SetLoading(true);
_xhr = $.ajax({
url: $("meta[name='root']").attr("content") + '/app/heatmap/spot',
type: 'POST',
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
data: {
venue_id: venueId
},
dataType: 'JSON',
async: true,
cache: false,
error: function (jqXHR, textStatus, errorThrown) {
_this.SetLoading(false);
},
success: function (response) {
_this.SetLoading(false);
console.log("Controls 90, Response of ajax call = ", response);
_this.Update(response);
}
});
};
// Public functions
this.SetLoading = function (option) {
if (_.isUndefined(option)) { option = false; }
if (this.spotName) { this.spotName.SetLoading(option); }
};
this.Update = function (data) {
if (_.isUndefined(data) || _.isNull(data)) {
console.log('Controls 106: Spot Name: ', data)
this.spotName = data;
}
};
var _getVenueData = function (venueId) {
for (var i = 0; i < venuesData.length; i++) {
if (venuesData[i].id === venueId) {
if (!_.isUndefined(venuesData[i].spot_data)) {
return venuesData[i].spot_data;
}
}
}
};
var _onVenueChange = function () {
var value = _this.GetVenue();
if (_.isNull(value)) {
return;
}
_getChartDisplayDiv(value);
//_setSelectValue(_venueSelect, value);
var venueData = _getVenueData(value);
console.log('Venue data received: ', venueData);
if (!_.isUndefined(venueData) && !_.isUndefined(venueData.floors)) {
_selects.UpdateSelect(_floorSelect, venueData.floors);
_onFloorChange();
}
};
var _onFloorChange = function () {
var value = _this.GetFloor(),
zones = [];
if (_.isNull(value)) {
return;
}
//_setSelectValue(_floorSelect, value);
if (_.isNumber(value)) {
var venueData = _getVenueData(_this.GetVenue()),
floors = venueData.floors;
for (var i = 0; i < floors.length; i++) {
if (floors[i].id === value) {
zones = floors[i].zones;
}
}
}
_selects.UpdateSelect(_zoneSelect, zones);
};
var _onZoneChange = function () {
var value = _this.GetZone();
if (_.isNull(value)) {
return;
}
//_setSelectValue(_zoneSelect, value);
};
var _onSelectChange = function (e) {
var t = $(e.target),
id = t.attr('id');
if (_venueSelect && _venueSelect.attr('id') === id) {
_onVenueChange();
} else if (_floorSelect && _floorSelect.attr('id') === id) {
_onFloorChange();
} else if (_zoneSelect && _zoneSelect.attr('id') === id) {
_onZoneChange();
}
EventDispatcher.Dispatch('Main.Controls.Change', _this, {
caller: id
});
};
// Public Methods
this.GetDateRange = function () {
return _selects.GetSelectValue(_dateRangeSelect);
};
this.GetDateRangeKey = function () {
if (_dateRangeSelect) {
var selected = _dateRangeSelect.find('option:selected');
if (selected.length) {
return selected.attr("data-key") || "";
}
}
return "";
};
this.GetVenue = function () {
return _selects.GetSelectValue(_venueSelect);
};
this.SetVenue = function (value) {
_selects.SetSelectValue(_venueSelect, value);
}
this.GetFloor = function () {
return _selects.GetSelectValue(_floorSelect);
};
this.SetFloor = function (value) {
_selects.SetSelectValue(_floorSelect, value);
}
this.GetZone = function () {
return _selects.GetSelectValue(_zoneSelect);
};
this.SetZone = function (value) {
_selects.SetSelectValue(_zoneSelect, value);
}
this.GetData = function () {
return {
dateRange: {
date: this.GetDateRange(),
key: this.GetDateRangeKey()
},
venue: this.GetVenue(),
floor: this.GetFloor(),
zone: this.GetZone()
};
};
// Init
_construct();
};
return MainControlsClass;
});
The function that determines which visual to display is close to the top: _getChartDisplayDiv:
var _getChartDisplayDiv = function (venueId) {
var path = window.location.pathname,
pathArray = path.split("/"),
page = pathArray[pathArray.length - 1];
_this.Load(venueId);
console.log('Controls 40, sPot Name = ', _this.spotName);
if (page === 'heatmap') {
if (venueId === 8 || venueId === 354) {
//make the bubble div visible
$("#heatmap-bubble").show();
//make the map div invisible
$("#heatmap-map").hide();
} else {
//make the map div visible
$("#heatmap-map").show();
//make the bubble div invisible
$("#heatmap-bubble").hide();
}
}
}
When I am able to pass the "spot" information to it or a variable that it uses, it should look like this:
var _getChartDisplayDiv = function (venueId) {
var path = window.location.pathname,
pathArray = path.split("/"),
page = pathArray[pathArray.length - 1];
_this.Load(venueId);
console.log('Controls 40, sPot Name = ', _this.spotName);
if (page === 'heatmap') {
if (_this.spotName === 'local' ) {
//make the bubble div visible
$("#heatmap-bubble").show();
//make the map div invisible
$("#heatmap-map").hide();
} else {
//make the map div visible
$("#heatmap-map").show();
//make the bubble div invisible
$("#heatmap-bubble").hide();
}
}
}
My ajax call is here:
this.Load = function (venueId) {
console.log("Controls 66, Venue Id sent = ", venueId);
if (_xhr) {
_xhr.abort();
_xhr = null;
}
_this.SetLoading(true);
_xhr = $.ajax({
url: $("meta[name='root']").attr("content") + '/app/heatmap/spot',
type: 'POST',
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
data: {
venue_id: venueId
},
dataType: 'JSON',
async: true,
cache: false,
error: function (jqXHR, textStatus, errorThrown) {
_this.SetLoading(false);
},
success: function (response) {
_this.SetLoading(false);
console.log("Controls 90, Response of ajax call = ", response);
_this.Update(response);
}
});
};
This successfully gets the right spot, but I have been unable to pass it to a variable I can use. I think I am getting mixed up between private and public variables. I tried to use the 'this.Update' function to pass the setting to the public 'this.spotName' variable, but that comes up null. I have also tried to simply return the result of the ajax call, but I get a "not a function" error. How can I make the result of the ajax call available to my '_getChartDisplayDiv' function?
Your problem is that you are trying to read the value of _this.spotName before it is assigned. Let us walk through the steps that happen.
When you call _getChartDisplayDiv(value), the _getChartDisplayDiv function first calls _this.Load(venueId). Load, in turn, submits an ajax request with a success callback, reproduced in abbreviated form below:
this.Load = function (venueId) {
// ...
_this.SetLoading(true);
_xhr = $.ajax({
...
success: function (response) {
_this.SetLoading(false);
console.log("Controls 90, Response of ajax call = ", response);
_this.Update(response);
}
});
};
When the response arrives, the success callback will be invoked, which in turn will call _this.Update, which will set the variable you are after. The syntax you used for this purpose is correct. However!
"When the response arrives" happens to be an unpredictable event in the future. It might be after 10 milliseconds, it might take 2 seconds, or the request might time out altogether. Even 10 milliseconds is already an eternity, compared to the time it takes your browser to execute all other code in your script. You can be quite sure that by the time $.ajax returns, the success callback has not run yet.
When you pass a callback (success) to a function ($.ajax) and the callback is not run before the function returns, this is called an asynchronous callback, "async" for short. When a callback might be invoked async, it is important for the function to guarantee that it always runs async, because this type of situation needs to be handled in an entirely different way from when the callback is invoked synchronously (i.e., before the function returns). You can read more about the technicalities in this blogpost. So this is exactly what $.ajax guarantees: it will never invoke the success (or error) callback before it returns, even in the hypothetical situation that the response would arrive fast enough.
Right after $.ajax returns, your Load function returns, at which point your _getChartDisplayDiv function continues to execute. Almost immediately after that, you intend to read _this.spotName. $.ajax has already returned, so you might hope that at this point, the success callback has already been invoked.
Unfortunately for you, async callbacks are more stubborn than that. Not only does an async callback not run until the function to which you pass it returns; it does not run until any currently executing function returns. Besides $.ajax, Load needs to return, _getChartDisplayDiv needs to return, any function that was calling _getChartDisplayDiv needs to return, and so forth. The entire call stack needs to unwind. Only then (and when the response actually arrives, which is likely to be many milliseconds later) will the success callback be invoked. This game rule is called the event loop in JavaScript.
The solution is simpler than you might expect: you just need to invert the order of control. Rather than trying to force the data out of a request when you want to update the chart, you can update the chart when the response arrives, and rather than trying to update the chart directly, you can just trigger the request. Specifically in your case, you just need to make three changes:
In the places where you currently call _getChartDisplayDiv, call _this.Load instead.
Remove the line that calls _this.Load inside the _getChartDisplayDiv function.
At the end of the success handler, add a line that calls _getChartDisplayDiv.
Incidentally, using a proper application framework will make it much easier to manage this kind of thing. In your case, I recommend trying Backbone; it builds on top of Underscore and jQuery and it is unopinionated, so you can gradually adopt it without having to radically change the way you work.
I am not familiar with underscore.js. For jQuery you have two options, which you can use as an inspiration for your case. Untested code:
1. Callback function
You provide a callback function:
$('.mydiv').myPlugin({ // Pass options Object to plugin
venuId: '123',
getType: function(type) {
console.log(type); // Example accessing internal data
}
});
Your plugin code:
(function( $ ) {
$.fn.myPlugin = function(opt) {
this.filter('div').each(function() {
const settings = $.extend({
namespace: 'myPlugin',
type: 'local'
getType: function() {},
// otherSettings: 'as needed',
}, opt);
// plugin code here...
if(typeof settings.getType === 'function') {
settings.getType(settings.type);
}
});
return this;
};
}( jQuery ));
2. Plugin method
You define plugin method(s) that can be called:
$('#mydiv').myPlugin({ // Pass options Object to plugin
venuId: '123'
});
console.log($('#mydiv').myPlaugin('getType'));
Your plugin code:
(function( $ ) {
$.fn.myPlugin = function(opt) {
this.filter('div').each(function() {
const settings = $.extend({
namespace: 'myPlugin',
type: 'local',
// otherSettings: 'as needed',
}, opt);
this.getType = function() {
return settings.type;
}
let firstArg = arguments[0];
if(typeof firstArg === 'string') {
let func = this[firstArg];
if(typeof func === 'function') {
var args = [];
for(var i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
return func.apply(this, args);
}
} else {
// plugin init code here...
}
});
return this;
};
}( jQuery ));

How to get data with $.ajax() jquery and display to the HTML webpage

I need help in retrieving data from https://jsonplaceholder.typicode.com/. Here is my AJAX call:
$.ajax({
url: root + '/posts/',
data: { userId: 1 },
type: "GET",
dataType: "json",
success: function(data) {
$("#posts").html("<p><h3>" + data[0].title + "</h3></p>");
console.log(data);
$("#posts").append("<p>" + data[0].body + "</p>");
console.log(data);
}
});
How can I set the data: userId to not only 1 but a range of values like 1 to 5 or 1 to 10? And how do I append all of them by displaying in my html file?
Simple click way
HTML:
<div data-action="getUsers" data-users="1,2,3,4,5">Get Users</div>
JS:
$(function(){
$("[data-action=getUsers]").on("click", function(){
// get userId someway
var $this = $(this);
var data = $this.data();
var users_string = data.users;
var userIds_str_array = users_string.spilt(","); // this will turn a string into an array of strings split by given char
var userIds_int_array = [];
var length = (userIds_str_array.length - 1);
// convert to ints, might be overkill, strings might be fine, this is your call
for(let i = length; i--;){ // short hand for-loop, use if order doesnt matter
let value = userIds_str_array[i];
let test_val = parseInt(value);
// short hand if
// return true to skip iteration
isNaN(test_val) === false ? userIds_int_array.push(value) : return true;
}
// make sure you are not wasting your time.
if(userIds_int_array.length > 1){
return GetUserFromPosts(user, function(responce){
if(responce.users){
if(responce.users.length > 0){
// func call
return doSomethingWithUsers(responce.users);
}else{
return false; // no users in array
}
}else{
return false; // there was probably a 500 error
}
});
}
else
{
return false // no users in array
}
})
})
function GetUserFromPosts(userId_arr, callback){
var data = {userId: userId_arr}; // I assume your api can accept an array, if not, you have to do seperate calls for each one.
$.ajax({
url: root + '/posts/',
data: data,
success: function(data) {
console.log(data);
return callback(data);
}
});
}

Ajax calls inside loop need sequential responses

I need to make 3 or less ajax calls, and the responses need to be appended to the dom in the same order they were requested.
I have the following function, but the problem is that the responses that I get are not necessarily in the correct order when they get appended to the dom.
I wouldn't want to use the async: false property because it blocks the UI and it's a performance hit of course.
mod.getArticles = function( ){
//mod.vars.ajaxCount could start at 0-2
for( var i = mod.vars.ajaxCount; i < 3; i++ ){
//mod.vars.pushIds is an array with the ids to be ajaxed in
var id = mod.vars.pushIds[i];
$.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
}
}).done( function( data ) {
if (data.length) {
mod.appendArticle( data );
} else {
console.error('get article ajax output error');
}
});
}
};
You need to append the article to a certain position, based on for example the i variable you have. Or you could wait for all of the requests and then append them in order. Something like this:
mod.getArticles = function( ){
var load = function( id ) {
return $.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
});
};
var onDone = function( data ) {
if (data.length) {
mod.appendArticle( data );
} else {
console.error('get article ajax output error');
}
};
var requests = [];
for( var i = mod.vars.ajaxCount; i < 3; i++ ){
requests.push(load(mod.vars.pushIds[i]));
}
$.when.apply(this, requests).done(function() {
var results = requests.length > 1 ? arguments : [arguments];
for( var i = 0; i < results.length; i++ ){
onDone(results[i][0]);
}
});
};
Here is an example using i to append them in the proper order when they all finish loading:
mod.getArticles = function( ){
// initialize an empty array of proper size
var articles = Array(3 - mod.vars.ajaxCount);
var completed = 0;
//mod.vars.ajaxCount could start at 0-2
for( var i = mod.vars.ajaxCount; i < 3; i++ ){
// prevent i from being 3 inside of done callback
(function (i){
//mod.vars.pushIds is an array with the ids to be ajaxed in
var id = mod.vars.pushIds[i];
$.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
}
}).done( function( data ) {
completed++;
if (data.length) {
// store to array in proper index
articles[i - mod.vars.ajaxCount] = data;
} else {
console.error('get article ajax output error');
}
// if all are completed, push in proper order
if (completed == 3 - mod.vars.ajaxCount) {
// iterate through articles
for (var j = mod.vars.ajaxCount; j < 3; j++) {
// check if article loaded properly
if (articles[j - mod.vars.ajaxCount]) {
mod.appendArticle(articles[j - mod.vars.ajaxCount]);
}
}
}
});
}(i));
}
};
var success1 = $.ajax...
var success2 = $.ajax...
var success3 = $.ajax...
$.when(success1, success2, success3).apply(ans1, ans2, ans3) {
finalDOM = ans1[0]+ans2[0]+ans3[0];
}
Check this for more reference. This is still async, but it waits for all of them to complete. You know the order of invocation already, as its done through your code, so add the dom elements accordingly.
Solutions that rely solely on closures will work up to a point. They will consistently append the articles of a single mod.getArticles() call in the correct order. But consider a second call before the first is fully satisfied. Due to asynchronism of the process, the second call's set of articles could conceivably be appended before the first.
A better solution would guarantee that even a rapid fire sequence of mod.getArticles() calls would :
append each call's articles in the right order
append all sets of articles in the right order
One approach to this is, for each article :
synchronously append a container (a div) to the DOM and keep a reference to it
asynchronously populate the container with content when it arrives.
To achieve this, you will need to modify mod.appendArticle() to accept a second parameter - a reference to a container element.
mod.appendArticle = function(data, $container) {
...
};
For convenience, you may also choose to create a new method, mod.appendArticleContainer(), which creates a div, appends it to the DOM and returns a reference to it.
mod.appendArticleContainer = function() {
//put a container somewhere in the DOM, and return a reference to it.
return $("<div/>").appendTo("wherever");
};
Now, mod.getArticles() is still very simple :
mod.getArticles = function() {
//Here, .slice() returns a new array containing the required portion of `mod.vars.pushIds`.
//This allows `$.map()` to be used instead of a more cumbersome `for` loop.
var promises = $.map(mod.vars.pushIds.slice(mod.vars.ajaxCount, 3), function(id) {
var $container = mod.appendArticleContainer();//<<< synchronous creation of a container
return $.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML'
}).then(function(data) {
if (data.length) {
mod.appendArticle(data, $container);//<<< asynchronous insertion of content
} else {
return $.Deferred().reject(new Error("get article ajax output error"));
}
}).then(null, function(e) {
$container.remove();//container will never be filled, so can be removed.
console.error(e);
return $.when(); // mark promise as "handled"
});
});
return $.when.apply(null, promises);
};
mod.getArticles() now returns a promise of completion to its caller, allowing further chaining if necessary.
Try utilizing items within mod.vars array as indexes; to set as id property of $.ajaxSettings , set returned data at this.id index within an array of responses. results array should be in same order as mod.vars values when all requests completed.
var mod = {
"vars": [0, 1, 2]
};
mod.getArticles = function () {
var results = [];
var ids = this.vars;
var request = function request(id) {
return $.ajax({
type: "GET",
url: "/ajax/article/" + id + "/",
// set `id` at `$.ajaxSettings` ,
// available at returned `jqxhr` object
id: id
})
.then(function (data, textStatus, jqxhr) {
// insert response `data` at `id` index within `results` array
console.log(data); // `data` returned unordered
// set `data` at `id` index within `results
results[this.id] = data;
return results[this.id]
}, function (jqxhr, textStatus, errorThrown) {
console.log("get article ajax error", errorThrown);
return jqxhr
});
};
return $.when.apply(this, $.map(ids, function (id) {
return request(id)
}))
.then(function () {
$.map(arguments, function (value, key) {
if (value.length) {
// append `value`:`data` returned by `$.ajax()`,
// in order set by `mod.vars` items:`id` item at `request`
mod.appendArticle(value);
} else {
console.error("get article ajax output error");
};
})
});
};
mod.getArticles();
jsfiddle http://jsfiddle.net/6j7vempp/2/
Instead of using a for loop. Call your function in response part of previous function.
//create a global variable
var counter = 0;
function yourFunc(){
mod.getArticles = function( ){
//mod.vars.ajaxCount could start at 0-2
//mod.vars.pushIds is an array with the ids to be ajaxed in
var id = mod.vars.pushIds[counter ];
$.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
}
}).done( function( data ) {
if (data.length) {
mod.appendArticle( data );
} else {
console.error('get article ajax output error');
}
//increment & check your loop condition here, so that your responses will be appended in same order
counter++;
if (counter < 3)
{ yourFunc(); }
});
};
}
I'm faced same problem i'm solve this problem using following way.
just use async for get sequence wise response
<script type="text/javascript">
var ajax1 = $.ajax({
async: false,
url: 'url',
type: 'POST',
data: {'Data'},
})
.done(function(response) {
console.log(response);
});

Clean way to make Subsequent AJAX Calls to API based on Data

So I have a conceptual question regarding the cleanest way to make subsequent AJAX calls to an API based on the returned data.
A quick example:
A function, which encompasses the call would look like this:
function makeCall(headers, min, max) {
$.ajax({
headers: headers,
url: "https://coolapi.com/data?begIndex" + min + "&endIndex=" + max + "&begTimestamp=1404198000000&endTimestamp=1409554800000",
type: "GET",
dataType: 'JSON'
});
}
makeCall(headers, 0, 20);
The beg / end index (min/max), determine the amount of data I'll get back in the array. The API will only return a maximum of 20 items in the array, but it will also return me a COUNT of how many items total exist in that array. An example of the data returned is below:
{
count = 133;
result = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19];
}
So my next call would be:
makeCall(headers, 20, 40);
and so on so forth, until I got all 133 items from the array.
The question is...what is the cleanest way to continue to make subsequent calls until I've gotten and stored all 133 items from the array? Given that the count could be any number, it's hard to imagine how I can do this. I was thinking of nesting more ajax calls in a "success" function, but it's not scalable if I get back a number like 300.
Does anyone have any advice on how to proceed?
Thanks in advance!
EDIT:
So based on the advice in the comment, I've attemped to make the call recursive--but it doesn't seem to function as intended:
var theData = [];
var minCounter=0;
var maxCounter= minCounter + 20;
function makeCall(headers, min, max) {
$.ajax({
headers: headers,
url: "https://coolapi.com/data?begIndex" + min + "&endIndex=" + max + "&begTimestamp=1404198000000&endTimestamp=1409554800000",
type: "GET",
dataType: 'JSON',
success: function (data) {
theData.push(data.result);
newMin = minCounter + 20;
if (data.count >= theData.length ) {
makeCall(headers, newMin, maxCounter);
}
}
});
}
makeCall(headers, minCounter, maxCounter);
How do properly increment the variable as well as set the flag?
SECOND EDIT:
The method below works using the second comment's suggestion, but there are some issues here as well...
function doAjax(headers, min, dObject) {
var max = min + 20;
$.ajax({
headers: headers,
url: "https://coolapi.com/data?begIndex" + min + "&endIndex=" + max + "&begTimestamp=1404198000000&endTimestamp=1409554800000",
type: "GET",
dataType: 'JSON',
success: function (data) {
results.push(data);
window.count = data.count;
dObject.resolve();
}
});
}
// array that will contain all deferred objects
var deferreds = [];
// array that will contain all results
var results = [];
// make the ajax calls
for (var i = 20; i < 133 ; i+= 20) {
var dObject = new $.Deferred();
deferreds.push(dObject);
doAjax(headers, i, dObject);
}
// check if all ajax calls have finished
$.when.apply($, deferreds).done(function() {
console.log(results);
});
var dObject = new $.Deferred();
doAjax(headers,0, dObject);
First, the data doesn't push to the array in order. There doesn't seem anyway to fix this. Also strangely enough, in the for loop--I have to set the number for it to actually work. Trying to store it in a variable doesn't seem to work as well...Suggestions here?
Here's a working implementation based around the code you started with. Code is commented to help you understand what is happening:
// Change these constants to suit your purposes.
var API_URL = 'https://coolapi.com/data';
var HEADERS = {};
var API_RESULTS_PER_REQUEST = 20;
var MAX_API_CALLS = 20;
// Count API calls to trigger MAX_API_CALLS safety lock.
var apiCalls = 0;
// Function we'll call to get all our data (see bottom).
function collectApiData(begTimestamp, endTimestamp) {
var dataReady = jQuery.Deferred();
var params = {
'begTimestamp': begTimestamp,
'endTimestamp': endTimestamp
};
var datasetsCollected = requestDatasets(params);
jQuery.when(datasetsCollected).then(function(data) {
dataReady.resolve(data);
});
return dataReady;
}
// Makes individual AJAX call to API
function callApi(params, headers) {
var $request = jQuery.ajax({
url: API_URL,
headers: headers,
data: params,
type: 'GET',
dataType: 'JSON'
});
return $request;
}
// Recursive function that makes API calls until data is collected, there is an
// error, or MAX_API_CALLS limit is hit.
function requestDatasets(params, resultsReady, resultsFetched) {
resultsReady = ( resultsReady !== undefined ) ? resultsReady : jQuery.Deferred();
resultsFetched = ( resultsFetched !== undefined ) ? resultsFetched : [];
// Trigger safety to avoid API abuse
if ( apiCalls >= MAX_API_CALLS ) {
console.error('Exceeded max API calls:', MAX_API_CALLS);
resultsReady.resolve(resultsFetched);
}
// Set index data
params.begIndex = resultsFetched.length;
params.endIndex = resultsFetched.length + API_RESULTS_PER_REQUEST;
// Request dataset from API
var apiRequest = callApi(params, HEADERS);
apiCalls += 1;
// Callback once API request has completed and data is ready
jQuery.when(apiRequest).done(function(data) {
var apiResultCount = data.count;
resultsFetched = resultsFetched.concat(data.result);
console.debug('Fetched', resultsFetched.length, 'of', apiResultCount, 'API results');
if ( apiResultCount > resultsFetched.length ) {
console.debug('Making another API call');
requestDatasets(params, resultsReady, resultsFetched);
}
else {
console.debug('Results all fetched!');
resultsReady.resolve(resultsFetched);
}
});
jQuery.when(apiRequest).fail(function(data) {
console.error('API error: returning current results.');
resultsReady.resolve(resultsFetched);
});
return resultsReady;
}
// Run script
var dataReady = collectApiData('1404198000000', '1409554800000');
jQuery.when(dataReady).then(function(data) {
console.log(data);
});
Here's a working fiddle that mocks the API using httpbin.org:
http://jsfiddle.net/klenwell/mfhLxun2/

How do I get the changed element in a multiselect, using KendoUI?

I have a multiselect-list that acts as holder for a list of tags. I can't seem to figure out how to properly get the value of the item being changed and passed along with the changed-event. Here's my Kendo multiselect:
#(Html.Kendo().MultiSelect()
.Name("tags")
.Placeholder("No tags selected for this unit")
.BindTo(new SelectList(Model.TagsAvailable))
.Events(e => e
.Select("select")
.Change("change"))
.Value(Model.TagsSelected.ToArray())
)
And here are my js-methods:
function select(e) {
var dataItem = this.dataSource.view()[e.item.index()];
var param = dataItem.Text;
var url = '/UnitDetails/TagUnit/#Model.UnitId';
$.ajax({
url: url,
data: { selectedItem: param },
type: 'GET',
dataType: 'json',
success: function (data) {
// ...
},
error: function () {
// ...
}
});
};
function change(e) {
var dataItem = this;
var param = dataItem.element.context.innerText;
var url = '/UnitDetails/UnTagUnit/#Model.UnitId';
$.ajax({
url: url,
data: { selectedItem: param },
type: 'GET',
dataType: 'json',
success: function (data) {
// ...
},
error: function () {
// ...
}
});
};
My problem beeing that I feel the assignment of param is just quick and dirty. Surely, there must be some other, more correct way of going about this?
There is no easy way (afaik) for knowing the changes. So, let do it by ourselves.
First, I'm going to save the old value using the following function.
function saveCurrent(multi) {
multi._savedOld = multi.value().slice(0);
}
Where multi is the multi-select that we pass to the function as argument (so you can use the function for multiple multiselects).
Then, you need to implement change as follow:
change : function (e) {
// Retrieve previous value of `multiselect` saved using previous function
var previous = this._savedOld;
// These are current values.
var current = this.value();
// Let's save it for the next time
saveCurrent(this);
// The difference between previous and current are removed elements
var diff = $(previous).not(current).get();
console.log("removed", diff);
// The difference between current and previous, are added elements
diff = $(current).not(previous).get();
console.log("added", diff);
}
Running example here http://jsfiddle.net/OnaBai/MapVN/
Nice answer OnaBai! very useful and helpful.
I had the same Nickla's problem.
But, I needed to "save current values" at dataBound event. And it works (logging the changed values).
If you start deleting an item, it fails because the function recognizes the "change" as an "item add".
This is what I did
function dataBound(ev) {
saveCurrent(this);
}
function saveCurrent(multi) {
multi._savedOld = multi.value().slice(0);
}
function change(ev) {
var previous = this._savedOld;
var current = this.value();
saveCurrent(this);
var diff = $(previous).not(current).get();
console.log("removed", diff);
var removedSkill = diff;
console.log("removedSkills", removedSkill);
diff = $(current).not(previous).get();
var addedSkill = diff;
console.log("added", diff);
console.log("addedSkills", addedSkill);
if (addedSkill.length > 1 || removedSkill.length > 1) {
if (addedSkill.length > 1) {
addedSkill = addedSkill[addedSkill.length - 1];
alert("Added skill code: " + addedSkill.toString());
}
if (removedSkill.length > 1) {
removedSkill = removedSkill[removedSkill.length - 1];
alert("Removed skill code: " + removedSkill.toString());
}
}
else {
if (addedSkill.length > 0) {
alert("Added skill code: " + addedSkill.toString());
}
if (removedSkill.length > 0) {
alert("Removed skill code: " + removedSkill.toString());
}
}
$.ajax({
url: "SomeUrl",
type: "POST",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({ removedSkill: removedSkill, addedSkill: addedSkill })
});
}
Thanks again!
Iván

Categories