Question: Why don't elements appended to the DOM from my AJAX success function appear until after the success function has returned?
Context: I am using AJAX to get a JSON object with about 6000 inner objects that I want to use to populate a table. Unfortunately it takes about 10 seconds to create the table, so I'd like to give the user some visual feedback while it loads. I thought the user would be able to see table rows "live" as I call append but they don't load until success returns. When that didn't work, I tried updating the width of a Bootstrap progress bar, but the bar simply freezes during processing.
Goal: I would like the user to either see the table rows as they are appended or a progress bar at updates properly.
Code:
AJAX call:
$.ajax({
type: "GET",
url: myUrl,
contentType: "application/json; charset=UTF-8",
dataType: "json",
success: function(results){
for(var i in results) {
$("#bar").css("width", s / results.length + "%");
console.log(i);
var t_cell = $('<td class="'+attrs[i]+'">');
t_cell.css("background-color", results[i]["val0"]);
t_cell.append($("<span class='val'>").text(results[i]["val1"]));
t_cell.append($("<span class='raw'>").text(results[i]["val2"]]));
t_row.append(t_cell);
$("#review_table > tbody").append(t_row);
}
$('#progress').hide();
}
});
HTML:
<div id="progress" class="progress progress-striped active">
<div id="bar" class="bar" style="width: 1%;"></div>
</div>
<div id='review_div'>
<table class='table table-condensed' id='review_table'>
<thead></thead>
<tbody></tbody>
</table>
</div>
Try
$.ajax({
type : "GET",
url : myUrl,
contentType : "application/json; charset=UTF-8",
dataType : "json",
success : function(results) {
var i = 0;
function process() {
while (i < results.length) {
console.log(results[i])
$("#bar").css("width", s / results.length + "%");
console.log(i);
var t_cell = $('<td class="' + attrs[i] + '">');
t_cell.css("background-color", results[i]["val0"]);
t_cell.append($("<span class='val'>").text(results[i]["val1"]));
t_cell.append($("<span class='raw'>").text(results[i]["val2"]));
t_row.append(t_cell);
$("#review_table > tbody").append(t_row);
i++;
if (i % 25 == 0) {
setTimeout(process, 0);
break;
}
}
}
process();
$('#progress').hide();
}
});
You could try something like this:
for(var i in results) {
createRow(i);
}
and
function createRow(i){
var t_cell = $('<td class="'+attrs[i]+'">');
t_cell.css("background-color", results[i]["val0"]);
t_cell.append($("<span class='val'>").text(results[i]["val1"]));
t_cell.append($("<span class='raw'>").text(results[i]["val2"]]));
t_row.append(t_cell);
$("#review_table > tbody").append(t_row);
}
However, I really don't think you should be rendering 6000 rows in a table though. Try paginating your table using a library. DataTables has a section on how to paginate Bootstrap tables specifically.
http://www.datatables.net/blog/Twitter_Bootstrap
Rather than thinking of it as pagination you could actually write a loop that fetches your records in smaller chunks; maybe try 100 records at a time. As you're appending your 100 records to the DOM, your loop keeps fetching and appending in the background. You would have to be careful to keep them in the order you want them since there is no guarantee data will be returned in the order requested. I would maybe try using .after() to append the data in the right order. With some tinkering I think this could produce a clean and efficient user experience.
Related
I am facing issue while append data to front-end. I am displaying the data into chunk of 10.
Issue is if record is 10, load more button should be hidden, but button is still visible. How to fix this issue?
Image:
Ajax:
<script>
var counter = 0;
window.onload = function() {
$.ajax({
url: "/get_jobs",
cache: false,
data: {
"_token": $('#csrf-token').content,
"counter": counter,
},
success: function(response){
if (response.count < 10){
$('#seeMore').hide();
}
else{
$('#seeMore').show();
var data = response.all_jobs;
append_data(data);
counter++;
}
}
});
};
async function append_data(data){
for (i = 0; i < data.length; i++) {
console.log("1 for");
var html = ' <div class="col-md-6 col-lg-6 content aos-init aos-animate" data-aos="fade-right" data-aos-duration="1100" data-aos-once="true" style="margin-bottom: 16px;"><div class="texts"><h3 class="gr-text-6 font-weight-bold text-blackish-blue mb-4">'+data[i].job_title+'</h3><p class="gr-text-9 mb-0 gr-color-blackish-blue-opacity-7">'+data[i].company+'</p></div><div class="card-icon"><i class="fas fa-arrow-right gr-text-8 text-storm"></i></div></div>';
$(html).hide().appendTo("#job_section").fadeIn(1000);
}
}
$("#seeMore").click(function(e){
$.ajax({
url: "/get_jobs",
cache: false,
data: {
"_token": $('#csrf-token').content,
"counter": counter,
},
success: function(response){
if (response.count < 10){
console.log("2 if");
$('#seeMore').hide();
}
console.log("2 else")
var data = response.all_jobs;
append_data(data);
counter++;
}
});
});
</script>
Controller:
public function getjobs(Request $request){
$all_jobs = DB::table('jobs')->skip($request->counter*10)->take(10)->where('job_status', 1)->orderBy('id','DESC')->get();
return response(["all_jobs" => $all_jobs, "count" => count($all_jobs)]);
}
all benefits of pagination is to not load all data with one request. and prevent sending a lot of data is optimizing in **page load, bandwidth saving, server resources, client resources ....
as #apokryfos suggested laravel has a bulit-in function (paginate) to take care of this.
but as i see, you are sending all of your records within just one request , and believe its not a good practice at all.
Fix
you wrote:
if (response.count < 10)
which takes care of 0-9 items, if you want to hide on 10 items you could:
if (response.count =< 10)
bit it would be a real paginate if you send data chunks. with attributes like total,page_number,...
it can be simply with using of laravel paginate:
return DB::table('jobs')->where('job_status',1)->orderBy('id','DESC')->paginate();
it gets the page_number from request
as Laravel Document suggested you can just grab the next page url from next_page_url and change the next button :
$("#seeMore").attr("href",next_page_url);
When my data coming from json and i tried to append row for my datatable with this code. But it didn't work. How can i fix this ?
<table style="width:300px" >
<thead>
<th>
Name
</th>
</thead>
<tbody id="location">
</tbody>
</table>
$.ajax(
{
type: "GET",
url: 'http://jsonplaceholder.typicode.com/users',
data: "{}",
contentType: "application/json; charset=utf-8",
dataType: "json",
cache: false,
success: function (data) {
var trHTML = '';
for (var i = 1; i > data.length; i++) {
console.log(data[i].name);
trHTML += '<tr><td><span>' + data[i].name + '</span></td></tr>';
};
$('#location tbody').append(trHTML);
},
error: function (msg) {
alert(msg.responseText);
}
});
My actual goal is adding row to footable datatable plugin but i can't fix it too.
There are a few issues with your code. Most are already mentioned in the commennts:
1) Your loop does not work because the condition i > data.length is never true. I suggest using jQuery.each(...) to loop over data. Or fix it to i < data.length but then you also have to start with var i = 0.
2) $('#location tbody') does not exist in your html. That would be a tbody element inside an element with the id location. You want $('tbody#location') or just $('#location') as you should not have multiple items with the same id in your html anyway.
3) Your html inside thead is not correct. It will work this way, but it is not clean html.
Hope this list helps. It basically summarises the comments. (Thanks to all you guys here!)
I have two different sets of a and p elements in my html page which are made as display:none by default.
At the end of the of the page I'm calling a function by sending their ID's and some values to enable any one of them based on some conditions
1st set
<a style="display:none;" id="ClickMe1">Click Me</a>
<p class="button" id="Sorry1" style="display:none;">Sorry!</p>
2nd set
<a style="display:none;" id="ClickMe2">Click Me</a>
<p class="button" id="Sorry2" style="display:none;">Sorry!</p>
Function call
<script>
window.onload = function () {
Initialize("ClickMe1", "Sorry1", "23,35");
Initialize("ClickMe2", "Sorry2", "76,121");
};
</script>
Initialize function consists of a ID, p ID and set of values(it can contain n values) to check which element to enable
Javascript Function
function Initialize(ClickMeID, SorryID,Values) {
var valList = Values.split(',');
for (i = 0; i < valList.length; i++) {
var paramts = "{'val':'" + valList[i] + "'}";
jQuery.ajax({
type: "POST",
url: "/services/MyService.asmx/GetData",
data: paramts,
contentType: "application/json; charset=utf-8",
dataType: "json",
async: false,
success: function (response) {
Status = response.d.toString();
},
error: function (response) {
}
});
if (Status == "0") {
$('#' + SorryID).show();
return;
}
}
$('#' + (ClickMeID).show();
}
In my function I'm splitting the comma seperated Values and looping through each value and making an ajax call to my service with async:false.
The response of success call is either 1 or 0. If any of Values is 0 of a function call I want to display p element else a element of the sent ID's.
This function is working fine but when the function call is raised this is making the browser freeze until the execution of the function.
If I make async: true I'm not able to find out which set of buttons to enable and disable
How can I make prevent the browser from freezing.
You should set
async: true
If it's not async, then it'll be blocking.
Also, if you're looping through many items, you should wrap each iteration in a setTimeout and make it async too.
Code samples
function click(e){
var button = e.target;
$.ajax({
type: "POST",
url: "http://localhost/accounts/save",
data : {
accountID: 123,
name:"hello world"
},
beforeSend: function(){
//disable the button.
}
}).always(function(){
//enable the button
})
}
here's an example of of setTimeout
setTimeout(function(){
//do something
}, 3000); //3seconds
I would highly recommend, that you read up on jquery.Deferred and event loops.
I'm not able to find out which set of buttons to enable and disable
Then that is your real issue. You solved your problem with other code to cause a new problem.
I highly suggest reading Decoupling Your HTML, CSS, and JavaScript.
Here is what I would do (since you tagged jquery might as well.. actually fully use it).
<style>
.is-hidden{ display: none; }
</style>
<div class="js-init-content" data-params="[{'val':23},{'val':35}]">
<a class="is-hidden js-clickme">Click Me</a>
<p class="button is-hidden js-sorry">Sorry!</p>
</div>
<div class="js-init-content" data-params="[{'val':76},{'val':121}]">
<a class="is-hidden js-clickme">Click Me</a>
<p class="button is-hidden js-sorry">Sorry!</p>
</div>
<script>
// when the document is ready...
$(document).ready(function(){
// loop through each init-content item
$(".js-init-content").each(function(){
var $this = $(this);
// get the data from the html element
// jquery will return an array containing objects
// because it's smart and cool like that
var params = $this.data('params');
var isAvailable = true;
// loop through each param
$.each(params, function(index, param){
// stop loop and ajax calls if any previous ajax call failed
if (!isAvailable) return false;
// make an ajax call, param will be the object from the array
$.ajax({
type: "POST",
url: "/services/MyService.asmx/GetData",
data: param,
contentType: "application/json; charset=utf-8",
// dataType: "json", -- jquery is smart it will figure it out
// async: false, -- Almost no reason to ever do this
).done(function(response){
isAvailable = response.d.toString() != "0";
}); // End Ajax-Done
}); // End js-init-content.each
var selector = isAvailable
? ".js-clickme"
: ".js-sorry";
$this.find(selector).removeClass("is-hidden");
}); // End doc-ready
</script>
I encapsulated the data in the html, instead of hardcoding it in the javascript. Fully used jQuery for loading and updating.
I've got two divs on my page
<div id="1"></div>
<div id="2"></div>
That I'm trying to dynamically fill with the following php call.
<script>
var queries = ["SELECT * from table1", "SELECT * from table2"]
for (var i = 0; i < queries.length; i++) {
$.ajax({
url: "querySQL.php",
type: "GET",
cache: false,
data: {query: queries[i]},
success: function(data) {
$("#" + i).html(data);
}
});
}
</script>
It looks like it's looping through the queries properly, however it 'erases' the first one and when I view the page, only the results of the second query remain. What am I doing wrong?
Notwithstanding the warnings about exposing a raw SQL interface in your API, the problem you have is that i in the callback once the AJAX call completes doesn't have the same value it did when you initiated the call.
The easiest solution is to use $.each or Array#forEach instead of a for loop, and use the index parameter that is then correctly bound to the current value in the callback:
$.each(queries, function(i, query) {
$.ajax({
url: "querySQL.php",
type: "GET",
cache: false,
data: { query: query },
success: function(data) {
$("#" + (i + 1)).html(data); // NB: i starts at 0, not 1
}
});
});
This will work
var queries = ["SELECT * from table1", "SELECT * from table2"];
function callAjax(i){
$.ajax({
url: "querySQL.php",
type: "GET",
cache: false,
data: {query: queries[i]},
success: function(data) {
console.log(i)
$("#" + (i+1).html(data);
}
});
}
for (var i = 0; i < queries.length; i++) {
callAjax(i)
}
It looks that you have only 2 results. The problem here is your var i. it starts with i=0 and ends in i=1. So only div $("#" + 0).html(data) and $("#" + 1).html(data).
To fix this start with
i=1; i <= queries.length; i++
i guess you need to send only one request on page load.And in your php file that is ajax url,you need to execute the both queries one by one and then populate data in the div in the same file.and just return all data.On ajax success,you just populate a div with returned data.
I would set the datatype to application/json, so the answer can be a json object, not only text/html. I would return the following parameters from the php as a json via json_encode, something like this:
{firstResult:{divToUse: "#1", dataToFill: "#1 content"},
secondResult:{divToUse: "#2", dataToFill: "#2 content"},
....
}
I hope it helps.
I've got two multi select list boxes, the first one allows someone to select a team.
The second one shows the members related to the team. When the first list box (the team) is selected I make an ajax call to fill the members of that team. I'm also using the chosen library. This is all working fine however, I needed a way to remove the x from the listbox selected value so that users don't think they can remove a member from the team.
$("#MainContent_lbMembers_chosen a").removeClass("search-choice-close");
The above code works when I throw that in a console window, but if I have it in my if condition it doesnt seem to work:
$("#MainContent_lbTeams").on('change', function() {
//was a value selected?
var latest_value = $("option:selected:last", this).val();
var latest_text = $("option:selected:last", this).text();
if ($("#MainContent_lbTeams :selected").length > 0) {
$("#dTeamNotice").show();
$("#MainContent_lblTeamMembers").text("Members of '" + latest_text + "':");
PopulateMembers(latest_value);
$("#MainContent_lbMembers_chosen a").removeClass("search-choice-close");
$("#trMembers").fadeIn();
} else {
//hide it...
$("#dTeamNotice").css("display", "none");
$("#trMembers").hide();
}
});
Basically the change event grabs the most recently selected text and value. If the length of what is selected > 0 I load the members of my team with PopulateMembers:
function PopulateMembers(buCompanyTeamID) {
$('#<%=lbMembers.ClientID %>').empty().append('<option selected="selected" value="0">Loading...</option>');
$("#<%=lbMembers.ClientID %>").trigger("chosen:updated");
$.ajax({
type: "POST",
url: "/Code/WebServices/Utilities.asmx/GetTeamMembers",
data: '{buCompanyTeamID: ' + buCompanyTeamID + '}',
contentType: "application/json; charset=utf-8",
dataType: "json",
success: OnMembersPopulated,
failure: function (response) {
alert(response.d);
}
});
}
function OnMembersPopulated(response) {
PopulateControl(response.d, $("#<%=lbMembers.ClientID %>"), true);
}
function PopulateControl(list, control, selected) {
if (list.length > 0) {
control.removeAttr("disabled");
control.empty().append('<option selected="selected" value="0"></option>');
$.each(list, function () {
if(selected)
control.append($("<option selected></option>").val(this['Value']).html(this['Text']));
else
control.append($("<option></option>").val(this['Value']).html(this['Text']));
});
}
else {
control.empty().append('<option selected="selected" value="0"><option>');
}
control.trigger("chosen:updated");
}
But I cannot understand why in a console window I can do this:
$("#MainContent_lbMembers_chosen a").removeClass("search-choice-close");
And it removes the x from the chosen selected value so that a user cannot remove an item, but within the if condition this doesnt have any effect.
I even tried disabling like so:
$("#MainContent_lbMembers").attr('disabled', true).trigger("chosen:updated");
This only works in a console as well, is it some timing issue or something else?
PopulateMembers() contains an asynchronous Ajax call. So, if you are expecting:
PopulateMembers(latest_value);
$("#MainContent_lbMembers_chosen a").removeClass("search-choice-close");
to operate on the results of the ajax call in PopulateMembers(), then you do indeed have a timing problem. The Ajax call will complete some time in the future, long after PopulateMembers() has finished and long after you've executed the .removeClass() statement.
To operate on the results of PopulateMembers(), you have to either put your code in the success handler of that ajax call or restructure your code so that PopulateMembers() will call a callback when it's done and you can do the .removeClass() in that callback.
I would suggest using promises like this:
// return the ajax promise from PopulateMembers
function PopulateMembers(buCompanyTeamID) {
$('#<%=lbMembers.ClientID %>').empty().append('<option selected="selected" value="0">Loading...</option>');
$("#<%=lbMembers.ClientID %>").trigger("chosen:updated");
return $.ajax({
type: "POST",
url: "/Code/WebServices/Utilities.asmx/GetTeamMembers",
data: '{buCompanyTeamID: ' + buCompanyTeamID + '}',
contentType: "application/json; charset=utf-8",
dataType: "json"
}).then(onMembersPopulated, function (response) {
alert(response.d);
});
}
$("#MainContent_lbTeams").on('change', function() {
//was a value selected?
var latest_value = $("option:selected:last", this).val();
var latest_text = $("option:selected:last", this).text();
if ($("#MainContent_lbTeams :selected").length > 0) {
$("#dTeamNotice").show();
$("#MainContent_lblTeamMembers").text("Members of '" + latest_text + "':");
// act only when the returned promise is resolved
PopulateMembers(latest_value).then(function() {
$("#MainContent_lbMembers_chosen a").removeClass("search-choice-close");
$("#trMembers").fadeIn();
});
} else {
//hide it...
$("#dTeamNotice").css("display", "none");
$("#trMembers").hide();
}
});