Creating ajax request loop within an 'each' function - javascript

This topic is covered in a few other questions, but I had some difficulty applying the suggested approaches into this use case. I have a checkbox list, where a user can select n sub-sites to publish their post to. since this list could grow to be 100+, I need an efficient way to perform an expensive task on each one. It's okay if it takes awhile, as long as Im providing visual feedback, so I planned to apply an "in progress" style to each checkbox item as its working, then move to the next item int he list once it is successfully published. Also note: I'm working in the WordPress wp_ajax_ hook but the PHP side of things is working well, this is focused on the JS solution.
This code is working right now (console.logs left in for debug), but I've seen multiple warnings against using async: true. How can I achieve a waterfall AJAX loop in a more efficient way?
//Starts when user clicks a button
$("a#as_network_syndicate").click( function(e) {
e.preventDefault(); //stop the button from loading the page
//Get the checklist values that are checked (option value = site_id)
$('.as-network-list').first().find('input[type="checkbox"]').each(function(){
if($(this).is(':checked')){
blog_id = $(this).val();
console.log(blog_id+' started');
$(this).parent().addClass('synd-in-progress'); //add visual feedback of 'in-progress'
var process = as_process_syndication_to_blog(blog_id);
console.log('finished'+blog_id);
$(this).parent().removeClass('synd-in-progress');
}
});
});
function as_process_syndication_to_blog(blog_id){
var data = {
"post_id": $('#as-syndicate_data-attr').attr("data-post_id"), //these values are stored in hidden html elements
"nonce": $('#as-syndicate_data-attr').attr("data-nonce"),
"blog_id": blog_id
};
var result = as_syndicate_to_blog(data);
console.log('end 2nd func');
return true;
}
function as_syndicate_to_blog(data){
$.ajax({
type : "post",
dataType : "json",
async: false,
url : ASpub.ajaxurl, //reference localized script to trigger wp_ajax PHP function
data : {action: "as_syndicate_post", post_id : data.post_id, nonce: data.nonce, blog_id: data.blog_id},
success: function(response) {
if(response.type == "success") {
console.log(response);
return response;
} else {
}
},
error: {
}
});
}

Indeed, doing synchronous AJAX request is bad because it will block the browser during the whole AJAX call. This means that the user cannot interact with your page during this time. In your case, if you're doing like 30 AJAX calls which take say 0.5 seconds, the browser will be blocked during 15 whole seconds, that's a lot.
In any case, you could do something following this pattern:
// some huge list
var allOptions = [];
function doIntensiveWork (option, callback) {
// do what ever you want
// then call 'callback' when work is done
callback();
}
function processNextOption () {
if (allOptions.length === 0)
{
// list is empty, so you're done
return;
}
// get the next item
var option = allOptions.shift();
// process this item, and call "processNextOption" when done
doIntensiveWork(option, processNextOption);
// if "doIntensiveWork" is asynchronous (using AJAX for example)
// the code above might be OK.
// but if "doIntensiveWork" is synchronous,
// you should let the browser breath a bit, like this:
doIntensiveWork(option, function () {
setTimeout(processNextOption, 0);
});
}
processNextOption();
Notice: as said by Karl-André Gagnon, you should avoid doing many AJAX requests using this technique. Try combining them if you can, it will be better and faster.

If you can't pass the whole block to the server to be processed in bulk, you could use a jQuery queue. This is using your sample code as a base:
var $container = $('.as-network-list').first();
$container.find('input[type="checkbox"]:checked').each(function(){
var $input = $(this);
$container.queue('publish', function(next) {
var blog_id = $input.val(),
$parent = $input.parent();
console.log(blog_id+' started');
$parent.addClass('synd-in-progress'); //add visual feedback of 'in-progress'
as_process_syndication_to_blog(blog_id).done(function(response) {
console.log(response);
console.log('finished'+blog_id);
$parent.removeClass('synd-in-progress');
next();
});
});
});
$container.dequeue('publish');
function as_process_syndication_to_blog(blog_id){
var data = {
"post_id": $('#as-syndicate_data-attr').attr("data-post_id"), //these values are stored in hidden html elements
"nonce": $('#as-syndicate_data-attr').attr("data-nonce"),
"blog_id": blog_id
};
return as_syndicate_to_blog(data).done(function(){ console.log('end 2nd func'); });
}
function as_syndicate_to_blog(data){
return $.ajax({
type : "post",
dataType : "json",
url : ASpub.ajaxurl, //reference localized script to trigger wp_ajax PHP function
data : {action: "as_syndicate_post", post_id : data.post_id, nonce: data.nonce, blog_id: data.blog_id}
});
}
I don't have a test environment for this so you may need to tweak it for your use case.

Related

AJAX call not firing from inside if statement

I have the following code. There is a button in the UI that when clicked executes the if statement. I pass in a URL from a database and compare it to the current URL the user is on. If they match I want to run the code below, else I want to open the correct tab then run the code below.
With this code below I mean everything below starting from $('#sceanrioDropdownList').change(function () {...}. The code then checks a drop down and gets the selected Id from which an AJAX call is made to my web API that uses that Id in a stored procedure to return the results. The returned data is then iterated over and stored in variables which I am using to append to specific inputs, buttons and drop downs.
This is what I have so far and I think I have developed this correctly. The issue that I am currently having is that the UI wants everything from ... to be run if the if statement is true. I have tried CTRL+C and CTRL+V to copy the code into the if statement. I have also tried putting it in a new function and referencing that function n the if statement. Both do not work and I was using console.log to inspect the returned data.
It does however when I attempt to call it from inside i statement it doesn't return any data or error. It just doesn't seem to fire.
Is there a way in which I can achieve the functionality I desire? Do you have any suggestions as to if I have done something wrong. Thanks in advance.
$('#automate').click(automateButton);
function automateButton() {
if (webpageUrl == activeTabUrl) {
// do nothing
} else {
// Window opens
window.open(webpageUrl);
}
}
$('#scenarioDropdownList').change(function() {
var scenarioId = $('#scenarioDropdownList option:selected').prop('id');
getData(scenarioId);
});
function getData(scenarioId) {
$.ajax({
type: "GET",
url: 'http://localhost:54442/api/scenariodatas/GetScenarioData',
data: {
scenarioId: scenarioId
},
dataType: 'JSON',
success: scenarioData,
error: function() {
console.log("There has been an error retrieving the data");
}
});
}
function scenarioData(response) {
$.each(response, function(key, val) {
var fieldType = val.fieldType;
var fieldName = val.fieldName;
var fieldValue = val.fieldValue;
var field = $(fieldName);
if (field != undefined) {
switch (fieldType) {
case "Input":
$(field).val(fieldValue);
break;
case "Button":
$(field).click();
break;
case "Select":
$(field).val(fieldValue);
break;
}
}
})
}
onChange don´t work well with buttons because onChange detect a change in the value of your component, because of this, it´s highly recommended to use onClick when you use a button.
$('#scenarioDropdownList').click(function() {
var scenarioId = $('#scenarioDropdownList option:selected').prop('id');
getData(scenarioId);
});
I recommend you to put alerts when you are trying to test this sort of JS
EJM:
$('#scenarioDropdownList').change(function() {
alert('button active');
var scenarioId = $('#scenarioDropdownList option:selected').prop('id');
getData(scenarioId);
});
this alert allow you to know if the code is firing or not

Can't get jQuery to execute code in the correct order

I am having some ordering issues. I have some code that does the following:
on page load, loop through 3 tables and grab content from server and populate table with said content
make the table responsive
I am having issues making this work. I can achieve this fine through inspect element (calling functions) but that's not user friendly. I want to know if there's a way I can choose the ordering on what function is being executed.
What I have so far is:
$(document).ready(function() {
if (dateCounter == null){ //start calendar from today's date
var current = new Date();
dateChange(current, "", 0); //function one to get grab all contents
//make table responsive
var switched = false;
var updateTables = function() {
if (($(window).width() < 992) && !switched ){
console.log("window width < 992px");
switched = true;
$("table.responsive").each(function(i, element) {
console.log("splitting table up");
splitTable($(element));
});
return true;
}
else if (switched && ($(window).width() > 992)) {
switched = false;
$("table.responsive").each(function(i, element) {
unsplitTable($(element));
});
}
};
function splitTable(original){...}
function unsplitTable(original){...}
}
});
In theory, on page load, it should populate the table first, then make the table responsive, but that's not the case. It seems to be rendering everything concurrently and therefore I get lots of missing/hidden content in my table. I don't know if the AJAX call in my dateChange function has anything to do preventing my table from displaying content correctly.
Following is a code snippet of the dateChange function:
function dateChange(dateInput, nGuests, vName){
//format dates
//For each table (3 tables)
$(".title").each(function(index, element) {
//prepare HTML for grabbing content from server
//Grab content from server and pop into table
$.ajax({
url: "/grab_Content.asp?boatType="+boatName+"&date="+dateInput+"&guests="+guests+"&boatName="+vName+"",
dataType:"html",
success: function(data){
table.html(data);
}
});
});
}
Yes, AJAX calls are asynchronous. $.ajax returns a promise that you can use to control sequence. First, return the promise from dateChange:
function dateChange(dateInput, nGuests, vName){
return $.ajax({
//...
});
}
Then when you call it:
dateChange(current, "", 0).then(function() {
//make table responsive
var switched = false;
//....
}
That will make sure the AJAX call completes before you make the table responsive.
If you have multiple AJAX calls, you'll have to store the promises in an array and use $.when:
var promises = [];
$('.whatever').each(function() {
var promise = $.ajax({ /*...*/ });
promises.push(promise);
});
$.when.apply($, promises).done(function() {
console.log('all done');
// do work....
});
Note we have to use Function.prototype.apply because $.when treats an array of promises like a single promise.

AJAX GET Call Will work on the first call but not after other clicks

I have an ajax call in my javascript that returns and loads a partial view into a div. This function used to work but then all the sudden it stopped. I do not think I changed any code or anything that would cause issue but obviously something is going on. The Ajax call will work on the first time when you click on the button in which it is called but never again until you reload the page. I have tried adding more parameters and moving the javascript around but it still did not work. Is there any reason why this could happen?
I have tried moving the javascript out of the onOpen event and the same thing still happens. I have also put an alert call to make sure it is getting to the success call and the alert is called. I have also installed fiddler to check the call and the call is never made except on the first click of the button. This is a very frustrating error and all help is much appreciated.
Here is my Javascript:
#section scripts
{
<script type="text/javascript">
$(document).ready(function () {
$("#assets-button").on("click", function ()
{
$('#assets-container').bPopup(
{
modal: true,
onOpen: function () {
$.ajax({
type: 'GET',
url: '#Url.Action("EmployeeAssets", "Employee",new { id = Model.ID, empNo = Model.EmployeeNumber, username = Model.UserName })',
success: function (data) {
$('#assets-container').html(data);
}
});
},
onClose: function () {
var f = $('#assets-container').children('form');
var serializedForm = f.serialize();
var action = '#Url.Action("EmployeeAssets","Employee",new {empNo = Model.EmployeeNumber})';
$.post(action, serializedForm);
}
});
});
});
</script>
}
Here is the action that I am trying to call:
[HttpGet]
public ActionResult EmployeeAssets(int id, int empNo, string username = null)
{
var assets = _employeeDb.EmployeeAssets.FirstOrDefault(e => e.EmpNo == empNo);
if (assets == null)
{
var firstOrDefault = _employeeDb.EmployeeMasters.FirstOrDefault(e => e.EmployeeNumber == empNo);
if (firstOrDefault != null)
{
username = firstOrDefault.UserName;
}
var newasset = new EmployeeAsset()
{
EmpNo = empNo,
UserName = username
};
_employeeDb.EmployeeAssets.Add(newasset);
_employeeDb.SaveChanges();
assets = newasset;
}
return PartialView(assets);
}
You may try using the cache property of the settings object you are passing to the AJAX call. According to the jQuery documentation for .ajax the default for cache is set to true, so I wonder whether your browser is accessing a cached copy of the result after the first request. Looks like you could also set the dataType, and that will default the cache back to false.
Also, I would suggest putting your alert inside of the onOpen event handler in addition to the success handler just to be sure that's also being called. So that may help you debug a bit further.

Best way to capture data from ajax and manipulate with it

I'm making a that should get images from database with ajax and then display it with some carousel plugin. Here is how it should work :
Image url is saved to database from admin
frontend script makes a ajax call to a php file and returns all url's from db.
Now when I have all images, the carousel plugin can be applied.
Images are now being displayed one by one.
The trick is that i want to make another ajax call when last image is displayed and repopulate container div with fresh images from database. Here is my code
function get_images(){
var data = {
"function_name" : "get_images",
"args" : ""
};
$.ajax({
type : "POST",
url : "http://localhost/.../functions.php",
data : data,
dataType : "JSON",
success : function(ret){
$(".container").html(ret);
$(".container").carousel(//When last call - function again);
}
});
}
And here is the problem. On ajax success carousel plugin is starting to rotate imgs, and when its finished, the get_images function should be call again. But this function is already in the another function and every time when it makes a call it will be one level deeper. Is there any way of doing this?
The best would be for the carousel to fire an event that it needs more images and listen to that event. You can then retrieve more images and return the new list to the carousel. There are carousel plugins that have all this build in. I often use this one: http://caroufredsel.dev7studios.com/
Maybe in your PHP script:
<?php
if ( isset($_POST['function_name']) ) {
if ( $_POST['function_name'] == 'get_images' ) {
// Get some URLs into an array...
// For the sake of the example we'll manually fill the array
$urls = ['img/image1.png', 'img/image2.png'];
echo json_encode($urls);
}
}
And in your JS:
function Carousel() {
this.getMoreImages();
}
Carousel.prototype.nextImage = function () {
if (this.i < this.images.length) {
this.showImage(); // Implement this.
this.i += 1;
this.spin():
}
else {
this.getMoreImages();
}
}
Carousel.prototype.spin = function () {
var self = this;
setTimeout(function () {
self.nextImage();
}, 5000);
}
Carousel.prototype.getMoreImages = function () {
var self = this;
$.ajax({
type : 'POST',
url : 'http://localhost/.../functions.php',
data : data,
dataType : 'JSON',
success : function (ret) {
self.images = JSON.parse(ret);
self.i = 0;
self.spin();
}
});
}
var myCarousel = new Carousel();
This Carousel object will request an array of images on instantiation, and show each image on a 5-second interval. When all the images have been exhausted, it will automatically make another AJAX call, retrieving images in the same manner as it did originally, and then continue looping through the new images. It will continue in this cycle forever.

Fire Jquery ajax only once, if already fired

I am facing some problems with my Ajax calls.
I am working to improve my site's performance, and for that 1 thing I want to do is fire ajax only once, if they have been fired before, not to fire them.
One of My Scenario.
I have 2 select drop downs - Country and zones.
when a country is changed, .change is fired which fires an ajax to get the zones of the selected country.
Now, for eg, If I select country India, Ajax is fired, and change it to Iceland, ajax is fired.
Now if i change my country back to india, Ajax should not be fired, as I have already fetched data from server.
CODE
$("#select_list_id").change(function(){
var url = 'ajax.php?val=' + this.value;
$.ajax({
url: url,
success: function(json){
for(i=0; i<jsonData.length;i++) {
$("#select_list_2_id").append(new Option(jsonData[i].title,jsonData[i].id));
}
}
});
})
My Approach can be to store the json being returned, and compare the countryID on change, if the value already exists, return the json back, else hit the Ajax.
But I have near about 100 ajax calls in my project, and this process might take some sweet time.
If anyone can help me, in how to generalize this code.
Apart from all this, while searching I found that this can be done via jquery once plugin as well, if so, can someone please provide some help on this.
Updated -
Thanks everyone, this did help me, and things are pretty good now :)
Just another small query, what is the use of jquery once plugin then ? Is it used for same stuff ?
if you can generalize your ajax calls, you could cache the result json by the request uri (url+params)
something like this:
var ajaxCache = [];
function ajaxCall(url,successFunc)
{
if(ajaxCache[url] != undefined)
{
successFunc(ajaxCache[url]);
}
else
{
$.ajax({
url: url,
success: function(json){
ajaxCache[url] = json;
successFunc(json);
}
});
}
}
$("#select_list_id").change(function(){
ajaxCall('ajax.php?val=' + this.value, function(json){
for(i=0; i<jsonData.length;i++) {
$("#select_list_2_id").append(new Option(jsonData[i].title,jsonData[i].id));
}
});
});
You can store the response on the option that has emitted that request:
$("#select_list_id").change(function() {
var $selected = $(this).children(":selected");
if ( $selected.data("json") ) {
build( $selected.data("json") );
}
else {
$.ajax({
url: 'ajax.php?val=' + this.value,
success: function(json) {
$selected.data("json", json);
build(json);
}
});
}
function build(jsonData) {
for(i=0; i<jsonData.length;i++) {
$("#select_list_2_id").append(new Option(jsonData[i].title,jsonData[i].id));
}
}
});
Assuming that the request is not cached, this is still relatively simple. You would maintain an object with country names as the keys and options as the values:
var countries = {};
.change(...
var country = this.value;
if (!countries.hasOwnProperty(country)) {
countries[country] = [];
$.ajax ...
for (var i = 0; ...
countries[country].push(new Option(...
}
//Iteration may be needed
$("#select_list_2_id").empty().append(countries[country]);

Categories