I have an application which is used for data analysis and I'm having a few performance issues with the creation of the table. The data is extracted from documents and it is important that all data is presented on one page (pagination is not an option unfortunately).
Using jQuery, I make an ajax request to the server to retrieve the data. On completion of the request, I pass the data to an output function. The output function loops through the data array using a for loop and concatenating the rows to a variable. Once the looping is complete, the variable containing the table is then appended to an existing div on the page and then I go on to bind events to the table for working with the data.
With a small set of data (~1000-2000 rows) it works relatively good but some of the data sets contain upwards of 10,000 rows which causes Firefox to either crash and close or become unresponsive.
My question is, is there a better way to accomplish what I am doing?
Here's some code:
//This function gets called by the interface with an id to retrieve a document
function loadDocument(id){
$.ajax({
method: "get",
url: "ajax.php",
data: {action:'loadDocument',id: id},
dataType: 'json',
cache: true,
beforeSend: function(){
if($("#loading").dialog('isOpen') != true){
//Display the loading dialog
$("#loading").dialog({
modal: true
});
}//end if
},//end beforesend
success: function(result){
if(result.Error == undefined){
outputDocument(result, id);
}else{
<handle error code>
}//end if
if($('#loading').dialog('isOpen') == true){
//Close the loading dialog
$("#loading").dialog('close');
}//end if
}//end success
});//end ajax
};//end loadDocument();
//Output document to screen
function outputDocument(data, doc_id){
//Begin document output
var rows = '<table>';
rows += '<thead>';
rows += '<tr>';
rows += '<th>ID</th>';
rows += '<th>Status</th>';
rows += '<th>Name</th>';
rows += '<th>Actions</th>';
rows += '<th>Origin</th>';
rows += '</tr>';
rows += '</thead>';
rows += '<tbody>';
for(var i in data){
var recordId = data[i].id;
rows += '<tr id="' + recordId + '" class="' + data[i].status + '">';
rows += '<td width="1%" align="center">' + recordId + '</td>';
rows += '<td width="1%" align="center"><span class="status" rel="' + recordId + '"><strong>' + data[i].status + '</strong></span></td>';
rows += '<td width="70%"><span class="name">' + data[i].name + '</span></td>';
rows += '<td width="2%">';
rows += '<input type="button" class="failOne" rev="' + recordId + '" value="F">';
rows += '<input type="button" class="promoteOne" rev="' + recordId + '" value="P">';
rows += '</td>';
rows += '<td width="1%">' + data[i].origin + '</td>';
rows += '</tr>';
}//end for
rows += '</tbody>';
rows += '</table>';
$('#documentRows').html(rows);
I was initially using a jQuery each loop but switched to the for loop which shaved off some ms.
I thought of using something like google gears to try offloading some of the processing (if that's possible in this scenario).
Any thoughts?
joinHi,
The rendering is a problem, but there is also a problem with concatenating so many strings inside the loop, especially once the string gets very large. It would probably be best to put the strings into individual elements of an array then finally use "join" to create the huge string in one fell swoop. e.g.
var r = new Array();
var j = -1, recordId;
r[++j] = '<table><thead><tr><th>ID</th><th>Status</th><th>Name</th><th>Actions</th><th>Origin</th></tr></thead><tbody>';
for (var i in data){
var d = data[i];
recordId = d.id;
r[++j] = '<tr id="';
r[++j] = recordId;
r[++j] = '" class="';
r[++j] = d.status;
r[++j] = '"><td width="1%" align="center">';
r[++j] = recordId;
r[++j] = '</td><td width="1%" align="center"><span class="status" rel="';
r[++j] = recordId;
r[++j] = '"><strong>';
r[++j] = d.status;
r[++j] = '</strong></span></td><td width="70%"><span class="name">';
r[++j] = d.name;
r[++j] = '</span></td><td width="2%"><input type="button" class="failOne" rev="';
r[++j] = recordId;
r[++j] = '" value="F"><input type="button" class="promoteOne" rev="';
r[++j] = recordId;
r[++j] = '" value="P"></td><td width="1%">';
r[++j] = d.origin;
r[++j] = '</td></tr>';
}
r[++j] = '</tbody></table>';
$('#documentRows').html(r.join(''));
Also, I would use the array indexing method shown here, rather than using "push" since, for all browsers except Google Chrome it is faster, according to this article.
Displaying that many rows is causing the browser's rendering engine to slow down, not the JavaScript engine. Unfortunately there's not a lot you can do about that.
The best solution is to just not display so many rows at the same time, either through pagination, or virtual scrolling.
The way you are building your string will cause massive amounts of garbage collection.
As the string gets longer and longer the javascript engine has to keep allocating larger buffers and discarding the old ones. Eventually it will not be able to allocate sufficient memory without recycling the remains of all the old strings.
This problem gets worse as the string grows longer.
Instead try adding new elements to the DOM one at a time using the jQuery manipulation API
Also consider only rendering what is visible and implement your own scrolling.
You can do couple of things to increase the performance:
your rows variable is getting bigger and bigger so, don't store the html in one variable. solution can be $.each() function and each function you append the element into DOM. But this is minor adjustment.
Html generating is good, but you can try DOM creating and appending. Like $('<tr></tr>').
And finally, this will solve your problem for sure : use multiple ajax call in the first ajax call collect how many data is available and fetch approximately 1,000 or may be more data. And use other calls to collect remaining data. If you want, you can use synchronous call or Asynchronous calls wisely.
But try to avoid storing the value. Your DOM size will be huge but it should work on moder browsers and forget about IE6.
#fuel37 : Example
function outputDocumentNew(data, doc_id) {
//Variable DOM's
var rowSample = $('<tr></tr>').addClass('row-class');
var colSample = $('<td></td>').addClass('col-class');
var spanSample = $('<span></span>').addClass('span-class');
var inputButtonSample = $('<input type="button"/>').addClass('input-class');
//DOM Container
var container = $('#documentRows');
container.empty().append('<table></table>');
//Static part
var head = '<thead>\
<tr>\
<th width="1%" align="center">ID</th>\
<th width="1%" align="center">Status</th>\
<th width="70%">Name</th>\
<th width="2%">Actions</th>\
<th width="1%">Origin</th>\
</tr>\
</thead>';
container.append(head);
var body = $('<tbody></tbody>');
container.append(body);
//Dynamic part
$.each(data, function (index, value) {
var _this = this;
//DOM Manupulation
var row = rowSample.clone();
//Actions
var inpFailOne = inputButtonSample.clone().val('F').attr('rev', _this.id).addClass('failOne').click(function (e) {
//do something when click the button.
});
var inpPromoteOne = inputButtonSample.clone().val('P').attr('rev', _this.id).addClass('promoteOne').click(function (e) {
//do something when click the button.
});
row
.append(colSample.clone().append(_this.id))
.append(colSample.clone().append(spanSample.colne().addClass('status').append(_this.status)))
.append(colSample.clone().append(spanSample.colne().addClass('name').append(_this.name)))
.append(colSample.clone().append(inpFailOne).append(inpPromoteOne))
.append(colSample.clone().append(_this.origin));
body.append(row);
});
}
in this process you need to create & maintain id's or classes for manipulation. You have the control to bind events and manipulate each elements there.
Answering to get formatting
What happens if you do
for(var i in data){
var record = data[i];
var recordId = record.id;
rows += '<tr id="' + recordId + '" class="' + record.status + '">';
rows += '<td width="1%" align="center">' + recordId + '</td>';
rows += '<td width="1%" align="center"><span class="status" rel="' + recordId + '"><strong>' + data[i].status + '</strong></span></td>';
rows += '<td width="70%"><span class="name">' + record.name + '</span></td>';
rows += '<td width="2%">';
rows += '<input type="button" class="failOne" rev="' + recordId + '" value="F">';
rows += '<input type="button" class="promoteOne" rev="' + recordId + '" value="P">';
rows += '</td>';
rows += '<td width="1%">' + record.origin + '</td>';
rows += '</tr>';
}//end for
Per others suggestions (I'm not reputable enough to comment yet, sorry!), you might try the TableSorter plugin to handle only displaying a usable amount of data at a time.
I don't know how it fares at very high numbers of rows, but their example data is 1000 rows or so.
This wouldn't help with JS performance but would keep the burden off the browser renderer.
Could try this...
Improve Loops
Improve String Concat
var tmpLst = [];
for (var i=0, il=data.length; i<il; i++) {
var record = data[i];
var recordId = record.id;
tmpLst.push('<tr id="');
tmpLst.push(recordId);
tmpLst.push('" class="');
tmpLst.push(record.status);
tmpLst.push('">');
tmpLst.push('<td width="1%" align="center">');
...ect...
}
rows += tmpLst.join('');
This might squeeze an extra bit of performance...
var lstReset = i * lstReset.length;
tmpLst[lstReset + 1]='<tr id="';
tmpLst[lstReset + 2]=recordId;
tmpLst[lstReset + 3]='" class="';
Related
I'm trying to output an array filled with Firestore objects onto a table, but just displays the last object above the table
<table class="darkTable">
<thead>
<tr>
<th>List of Available Shows</th>
</tr>
</thead>
<tbody>
<tr>
<div id="showList"></div>
</tr>
</tbody>
</table>
<script>
firebase.firestore().collection('TV Shows').get().then(snapshot => {
var i = 0;
var array = [];
snapshot.forEach(doc => {
array[i] = doc.data().show.name;
//console.log(doc.data().show.name);
//showList.innerHTML = array[i] + "<br />";
showList.innerHTML = '<td>' + array[i] + '</td>';
i++;
});
});
</script>
Is it the way I'm going about the td code lines?
assuming this markup:
<div id="showList"></div>
then it works about like this:
firebase.firestore().collection('TV Shows').get().then(snapshot => {
var showList = document.getElementById('showList');
var html = '<table class="darkTable"><thead><tr>';
html += '<th>List of Available Shows</th>';
/* add further columns into here, alike the one above. */
html += '</tr></thead><tbody>';
snapshot.forEach(doc => {
html += '<tr>';
html += '<td>' + doc.data().show.name + '</td>';
/* add further columns into here, alike the one above. */
html += '</tr>';
});
html += '</tbody></table>';
showList.append(html);
});
You're resetting the entire showList element with every iteration of the loop:
showList.innerHTML = '<td>' + array[i] + '</td>';
I suspect you mean to append to it each time instead or resetting it entirely each time. Maybe try building a string with each iteration, then set the whole thing after the loop is over.
I need to append every tag in my table inside for loop, but the loop and replaceWith is not working.
How to iterate the loop for each of and replacewith or append to the target ?
for ( z = 0; z < json[x].category[y].item.length; z++ ) {
html += '<td id="'+ json[x].category[y].item[z].idCat +'">';
html += '<div class="new-value">'+ json[x].category[y].item[z].value +'</div>';
html += '</td>';
}
$('table tr td.new').replaceWith(html);
Do like below:-
count = 0;
for ( z = 0; z < json[x].category[y].item.length; z++ ) {
count++;
var html = ''; //define html variable first
html += '<td id="'+ json[x].category[y].item[z].idCat +'">';
html += '<div class="new-value">'+ json[x].category[y].item[z].value +'</div>';
html += '</td>';
$('table tr td.new:eq("'+count+'")').html(html); // use .html()
}
As you check from below code, it works okay, no problem.
var html = '';
for ( z = 0; z < 3; z++ ) {
html += '<td id="tdnew_' + z + '">';
html += '<div class="new-value">NEW-'+ z +'</div>';
html += '</td>';
}
$('table tr td.new').replaceWith(html);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table border="1"><tr>
<td>OLD-9</td>
<td>OLD-8</td>
<td class="new">OLD7</td>
<td class="new">OLD8</td>
</tr></table>
So I suspect that there might be 2 reasons.
Case 1
Your javascript code is executed before <table> is not there yet, thus jQuery selector $('table tr td.new') actually points nothing.
Simple you can add following code before replaceWitdh() code line, to see if really td.new is existing there when the script is run:
alert($('table tr td.new').length)
If it alerts 0, means, table is not there yet when script is running.
Case 2
Your json might include some invalid chars, which makes your code not working, and this case you will see some error console I believe.
I need a table where the user enters how many rows and columns needed, they enter the numbers and the next page creates the table.
They will enter the info which will be saved into a database. The only way I can think to do this is with dynamic tables, is there a better way? Here is some super basic code, I haven't worked out the full table, wanted to get feedback before I continue in case there is a better way and I need to change course.
Simple form:
How many rows <input type="number" id="rowNumber"/><br>
How many columns <input type="number" id="colNumber"/><br>
<button onclick="myFunction()">Checkout</button>
function myFunction() {
var rowNumber = document.getElementById('rowNumber').value;
var colNumber = document.getElementById('colNumber').value;
window.location.href = "website/test.php?rowNumber="+rowNumber+"&colNumber="+colNumber;
}
test.php
<?php
$rowNumber=$_GET['rowNumber'];
$colNumber=$_GET['colNumber'];
?>
<script>
var numRows = "<? echo $rowNumber ?>";
var numCols = "<? echo $colNumber ?>";
var tableString = "<table>",
body = document.getElementsByTagName('body')[0],
div = document.createElement('div');
for (row = 1; row < numRows; row += 1) {
tableString += "<tr onclick=\"fnselect(this)\"<? if($rowID == "A") { echo "class ='selected'";} ?>>";
for (col = 1; col < numCols; col += 1) {
tableString += "<td>" + "R" + row + "C" + col + "" + "<input type='text' />" + "</td>";
}
tableString += "</tr>";
}
tableString += "</table>";
div.innerHTML = tableString;
body.appendChild(div);
</script>
Looking into jQuery DataTables. A lot of nice functionality in there.
You can either bind to a JSON data source, or create your own rows manually like this URL:
https://datatables.net/examples/api/add_row.html
So, to use this, you have to reference jquery AND the data tables script. You'll have to either reference them from their given URLs, or download the scripts (I recommend the latter otherwise you create references to outside servers).
I tried to get the value of the input-field that was dynamically created in function rijToevoegen().
Somehow I keep getting undefined, what am I doing wrong?
These are my functions I use:
//adding the tablerow
function rijToevoegen(columnarray, fieldarray, tabelnaam){
var columns = columnarray;
var fields = fieldarray;
var row = '<tr>';
for(i=0;i<columns.length;i++){
row += "<td class=columns[i]><input type='text' id=fields[i]></td>";
console.log(fields[i]);
}
row += '</tr>';
$(tabelnaam).append(row);
}
//getting the value
$('#vs_opslaan').click(function() {
var columns = ['naamkolom','locatiekolom','hostkolom','cpukolom','memorykolom','oskolom','hddkolom','spkolom','usernamekolom','passwordkolom','ipkolom','domeinkolom','opmerkingenkolom'];
var velden = ['naamveld','locatieveld','hostveld','cpuveld','memoryveld','osveld','hddveld','spveld','usernameveld','passwordveld','ipveld','domeinveld','opmerkingenveld'];
var response_array = [];
for(i=0;i<velden.length;i++){
var rij = $('#velden[i]').val();
console.log(rij);
//response_array += $().value;
}
//console.log(response_array);
});
Help is always appreciated!
Ramon
When you are referencing the arrays they cannot be inside the string or they will be set as literal strings.
When you put "<td class=columns[i]><input type='text' id=fields[i]></td>"the class and id contain literal strings columns[i] and fields[i]
What you want is to concatenate your array values with the markup.
"<td class=" + columns[i] + "><input type='text' id=" + fields[i] + "></td>"
This also applies to when you are doing the jquery selector.
So instead of $('#velden[i]').val(); you want $('#' + velden[i]).val();
i'm designing a bus seat layout using jquery. And i done it correctly too.
I'm using an array with seat numbers and i'm getting the seat layout what i want. This is my coding (here '.bus-table' is a table class) :
var row = Array(),i=0, j=0;
row = [
['1','5','9','13','17','21','25','29','33','37','41','45','49'],
['2','6','10','14','18','22','26','30','34','38','42','46','50'],
['','','','','','','','','','','','','51'],
['3','7','11','15','19','23','27','31','35','39','43','47','52'],
['4','8','12','16','20','24','28','32','36','40','44','48','53']
];
$.each(row, function(index, value) {
$('.bus-table').append('<tr>');
while(j<index+1) {
for(i=0; i<value.length; i++) {
$('.bus-table tr:nth-child('+ (index+1) +')').append(
'<td seatno="'+ row[j][i] +'">' + row[j][i] + '<input type="checkbox"/></td>' );
}
j++;
}
});
This is my results : this is my seat layout output
Now the problem is In 3rd row you can see some checkboxes only. Because in those areas there are no seats in the bus. So, i want to remove those checkboxes, which means in the array (3rd row) i left some blanks, according to those blanks i don't want the checkboxes too. I don't know how to do that. Please help me to solve this case.
(I apologies for my English)
Add an if statement inside the for loop:
for(i=0; i<value.length; i++) {
if (row[j][i] !== '') {
$('.bus-table tr:nth-child('+ (index+1) +')').append(
'<td seatno="'+ row[j][i] +'">' + row[j][i] + '<input type="checkbox"/></td>' );
}
else {
$('.bus-table tr:nth-child('+ (index+1) +')').append('<td></td>');
}
}
Note the else statement, otherwise it will break your table layout
You can skip those places using continue like shown below:
for(i=0; i<value.length; i++) {
if (row[j][i] == ""){
'<td seatno="'+ row[j][i] +'">' + row[j][i] + '</td>';
continue;//for blank don't make checkbox elements
}
$('.bus-table tr:nth-child('+ (index+1) +')').append(
'<td seatno="'+ row[j][i] +'">' + row[j][i] + '<input type="checkbox"/></td>' );
}