JavaScript offset/pagination issue - javascript

I am calling to my local API and trying to do it in a pagination style. I have n pictures that I want divided over n / 4 rows (4 pictures per row).
So therefor, I am calling to my API, images/count,offset. But somehow I keep on getting the same results in console.log, namely the first four images.
$(document).ready(function() {
var offset = 0;
var timesToRun = $('.container').data('rows');
var images = [];
for(var i = 1; i <= timesToRun; i++) {
$.ajax('http://192.168.10.11/images/4,' + offset, {
error: function(err) {
console.log(err);
},
method: 'GET',
success: function(data) {
console.log('http://192.168.10.11/images/4,' + offset);
offset = offset + 4;
var currentSet = [];
currentSet.push(data);
console.log(currentSet);
}
});
}
});
In Laravel I am pulling the number of images like so:
public function selectWithOffset($count, $offset)
{
$selectionOfImages = \DB::table('images')->skip($offset)->take($count)->get();
return response()->json($selectionOfImages);
}
When I click the links I do receive the expected response.
What might go wrong here?

The problem is within your JavaScript. $.ajax is asynchronous by default.
The for loop will complete before any success callback of $.ajax is called, and this is the place where you increase the offset.
You have to options to fix this:
1. Make $.ajax synchronous
Add async: false to the $.ajax options.
$.ajax('http://192.168.10.11/images/4,' + offset, {
error: function(err) {
console.log(err);
},
async: false,
method: 'GET',
success: function(data) {
// ...
}
});
2. Increment offset outside of the success callback
for(var i = 1; i <= timesToRun; i++) {
$.ajax('http://192.168.10.11/images/4,' + offset, {
// ...
});
// Increment offset
offset += 4;
}

Related

How to Execute ajax call after the scroller reaches the bottom

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)

Highcharts shift taking off too many points at the start

My chart has multiple series and when I try to add a new point and enable shift to true in the addpoint function on the series it seems to take too many points off. My requestData function runs an ajax query which hits my api and brings back data. The initial load loads in data just fine, however after the first points being added to the series it seems to take off too many points off some of the series.
Here is what it looks initial load
https://gyazo.com/51a2afc465f8fbb625e384e75d52b865
and here is what it looks like after one and two requests:
https://gyazo.com/0b523e4675c681b7495a38660b4de72c
https://gyazo.com/5cf7e9498ab3b34f3eaac1cf50a0bdc5
Here is my code for requesting data:
function requestData(chart1, start, devID, attrib) {
$.ajax({
url: 'api/MetricsAPI/',
data: {
deviceID: devID,
attribute: attrib,
startDate: start
},
success: function (dataset) {
var chartSeries = chart1.series;
for (var index = 0; index < dataset.length; index++) {
for (var i = 0; i < chartSeries.length; i++) {
if (chartSeries[i].name == dataset[index].attribute) {
var shift = chartSeries[i].data.length > 12;
chartSeries[i].addPoint([parseInt(dataset[index].epochTime), dataset[index].value], true, shift);
console.log('date: ' + dataset[index].epochTime + ', ' + 'value: ' + dataset[index].value);
}
}
}
chart1.redraw();
//call it again after one minute
setTimeout(requestData, 30000, chart1, dataset[dataset.length - 1].epochTime, devID, attrib);
},
failure: function (xhr, error) {
console.log(xhr);
console.log(error);
},
cache: false
});
}
Two possible issues with current code:
set redraw param to false, here: chartSeries[i].addPoint([parseInt(dataset[index].epochTime), dataset[index].value], true, shift);
set series.id and avoid comparing chartSeries[i].name == dataset[index].attribute. Instead to get correct series use chart.get(id).

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 pass a variable inside of an ajax call which is inside of a for loop (javascript)?

Working with an api and I need to one of the first responses alongside with the second response in order to serve up a new page. The problem I'm facing is that my variable $x is always set to whatever the last # is in the loop, ie 103 in this specific case. Here is my code:
$.ajax({
dataType: 'text',
type: 'post',
url: 'getAllMessages.php',
success: function(responseData) {
var newString = responseData;
var newerString = newString.substring(0, newString.length - 1);
$newObject = jQuery.parseJSON(newerString);
//console.log($newObject);
for($x = 0; $x < $newObject.messages.length; $x++){
$.ajax({
data: {clientFolderId: $newObject.messages[$x].clientFolderId, messageId: $newObject.messages[$x].messageId},
dataType: 'text',
type: 'post',
url: 'testapi.php',
success: function(responseData2){
//alert($x);
var newString2 = responseData2;
var newerString2 = newString2.substring(0, newString2.length - 1);
$newObject2 = jQuery.parseJSON(newerString2);
if($newObject2.statistics.delivered > 1000){
console.log($newObject.messages[$x]);
console.log($newObject2);
}
},
error: function(responseData2){
alert('failure in testapi.php');
}
});
}
},
error: function(responseData) {
alert('failure in getAllMessages.php');
}
});
My intuition says nesting the Ajax call inside another functional scope (correction thanks to Matt) will resolve the unexpected behavior. I got burned by this already Object creation in loop broken; unrolled works
Also here, example #5: http://www.javascriptkit.com/javatutors/closures2.shtml
Following the pattern given by Engineer,
for($x = 0; $x < $newObject.messages.length; $x++){
(function($x) {
$.ajax({
data: {clientFolderId: $newObject.messages[$x].clientFolderId, messageId: $newObject.messages[$x].messageId},
dataType: 'text',
type: 'post',
url: 'testapi.php',
success: function(responseData2){
alert($x);
var newString2 = responseData2;
var newerString2 = newString2.substring(0, newString2.length - 1);
$newObject2 = jQuery.parseJSON(newerString2);
if($newObject2.statistics.delivered > 1000){
console.log($newObject.messages[1]);
console.log($newObject2);
}
},
error: function(responseData2){
alert('failure in testapi.php');
}
});
})($x);
}
What you're experiencing is closure. When the loop spins round, the value for $x is updated. However, when the ajax function comes to grab it - it's using a reference. So as you find, you end up with the last value.
Try and think more functional? What are you trying to do? Let's say you're trying to postMessage - wrap that in a function and pass in the message.
Your code will become easier to read, and you won't get your variables mangled.
I was about to throw out some code, but noticed something I wanted to clarify - you're using the index in both loops to get a single message from a messages array, yet the POST to testapi.php seems to be working on a single message? What kind of response is expected from that?
 Update: Created jsFiddle to Demo Problem
Here you go here's some code to help you out.
function correctOutputPlox(id) {
setTimeout(function() {
$("#output").append("<li>" + id + "</li>");
}, 500);
}
function runNicely() {
// same loop...
for (var x = 0; x < 10; x++) {
// but rather than use 'x' (which is going to change, we pass it's value into a function which doesn't have access to the original 'x' since it's in a different lexical scope.
correctOutputPlox(x);
}
}
function showProblem() {
for (var x = 0; x < 10; x++) {
setTimeout(function() {
$("#output").append("<li>" + x + "</li>");
}, 500);
}
}
showProblem();
runNicely();

Categories