DOM memory issue with IE8 (inserting lots of JSON data) - javascript

I am developing a small web-utility that displays some data from some database tables.
I have the utility running fine on FF, Safari, Chrome..., but the memory management on IE8 is horrendous. The largest JSON request will return information to create around 5,000 or so rows in a table within the browser (3 columns in the table).
I'm using jQuery to get the data (via getJSON). To remove the old/existing table, I'm just doing a $('#my_table_tbody').empty(). To add the new info to the table, within the getJSON callback, I am just appending each table row that I am creating to a variable, and then once I have them all, I am using $('#my_table_tbody').append(myVar) to add it to the existing tbody. I don't add the table rows as they are created because that seems to be a lot slower than just adding them all at once.
Does anyone have any recommendation on what someone should do who is trying to add thousands of rows of data to the DOM? I would like to stay away from pagination, but I'm wondering if I don't have a choice.
Update 1
So here is the code I was trying after the innerHTML suggestion:
/* Assuming a div called 'main_area' holds the table */
document.getElementById('main_area').innerHTML = '';
$.getJSON("my_server", {my: JSON, args: are, in: here}, function(j) {
var mylength = j.length;
var k =0;
var tmpText = '';
tmpText += /* Add the table, thead stuff, and tbody tags here */;
for (k = mylength - 1; k >= 0; k--)
{
/* stack overflow wont let me type greater than & less than signs here, so just assume that they are there. */
tmpText += 'tr class="' + j[k].row_class . '" td class="col1_class" ' + j[k].col1 + ' /td td class="col2_class" ' + j[k].col2 + ' /td td class="col3_class" ' + j[k].col3 + ' /td /tr';
}
document.getElementById('main_area').innerHTML = tmpText;
}
That is the gist of it. I've also tried using just a $.get request, and having the server send the formatted HTML, and just setting that in the innerHTML (i.e. document.getElementById('main_area').innerHTML = j;).
Thanks for all of the replies. I'm floored with the fact that you all are willing to help.

var tmpText = [];
for (k = mylength - 1; k >= 0; k--)
{
/* stack overflow wont let me type greater than & less than signs here, so just assume that they are there. */
tmpText.push('anything you want')
tmpText.push( 'tr class="' + j[k].row_class . '" td class="col1_class" ' + j[k].col1 + ' /td td class="col2_class" ' + j[k].col2 + ' /td td class="col3_class" ' + j[k].col3 + ' /td /tr';)
}
$('#main_area').html(tmpText.join(''))
}
you dont need document.getElementById('main_area').innerHTML = '';
this method is to push into array, then join and use jquery html function to update. This is the fastest method I know. Sorry for the format here - its my first post and I thought I'd give something back here to stackoverflow.

To get IE to respond quickly you should be creating your table rows as string representations of HTML , appending them to a string variable, and then adding the result to your table's like this.
myTable.myTbody.innerHTML = allThoseRowsAsAString;
It's not a memory issue: 5,000 rows should be trivial. That's got to be far less than one megabyte.

Robusto is right about innerHTML assignment being a better way to go. IE sucks at dynamic DOM creation
Why not form your innerHTML on the server using a jsp and stream it back via ajax in one shot. It will definitely speed things up, remove complexity from your javascript and delegate markup creation to its proper place.

As Plodder said, IE has big problems when working with DOM. jQuery best practices recommends creating code on a simple string and appending just once inside the container.
Beside this, I recently had a similar problem for a hierarchycal data, having an amount of 5,000 data records. I asked myself: did the user really need all that information available at a given moment? Then I realized the best I could do was just present a "first chunk of data" and then insert more data on user demand.
Finally, just one good tool: Dynatrace Ajax (it helps a lot to find the javascript function it takes more time to operate)

Since you are dealing with thousands of data rows I wouldn't call $('#my_table_tbody').empty() and add the new data with new DOM elements. Instead I'd follow the Object Pool Pattern. Thus instead of dropping all the tr's you can reuse existing ones and just populate with the new data.
If your new data set has less rows then the previous one, remove the rest of the rows from the DOM, but keep references to them in some pool so that garbage collector won't destroy them. If your new data set is bigger - just create new tr's on demand.
You can look at the implementation of YUI DataTable, here's the source. IIRC they use this approach to speed up the render time.

Related

Convert javascript arrays into HTML table using DOM

I am currently creating a desktop app using tide sdk. All of my database information is stored into Parse.com (a serverless database). What I am trying to do is to take the array of the information I queried from Parse (in javascript) and insert it into a table. I am really having a hard time getting used to not using document.write() for my desktop application.
I want the end result to look like:
This is what I started with:
var contactNameArray = [];
var contactNumberArray= [];
var CM = Parse.Object.extend("ContactMenu");
var queryContact = new Parse.Query(CM);
queryContact.ascending("ContactName");
queryContact.find({
success: function(results4) {
alert("Successfully retrieved " + results4.length + " entries.");
// Do something with the returned Parse.Object values
// document.write('<table border="1" cellspacing="1" cellpadding="5">');
for (var i = 0; i < results4.length; i++) {
var object4 = results4[i];
contactNameArray[i] = object4.get('ContactName');
contactNumberArray[i] = object4.get('ContactNumber');
// document.write("<tr><td>Number " + i + " is:</td>");
//document.write("<td>" + contactNameArray[i] + "</td></tr>");
}
//document.write('</table>');
},
error: function(error) {
alert("Error: " + error.code + " " + error.message);
}
});
After doing some research I cam upon this bit of code from http://www.w3schools.com/jsref/dom_obj_table.asp which wrote: the correct response on the bottom of the left handed corner of the screen. (Kind of strange in my opinion). In code how can I better position this table to be in the center for my screen? Is there a way to center this table in javascript?
function generate_table() {
var x = document.createElement("TABLE");
x.setAttribute("id", "myTable");
document.body.appendChild(x);
var y = document.createElement("TR");
y.setAttribute("id", "myTr");
document.getElementById("myTable").appendChild(y);
var z = document.createElement("TD");
for(var i = 0; i< query4.length; i++){
var t = document.createTextNode(contactNameArray[i]);
z.appendChild(t);
var m = document.createTextNode(contactNumberArray[i]);
z.appendChild(m);
}
document.getElementById("myTr").appendChild(z);
}
So I have already figured out how to put the information I want into an array. I am just having a hard time putting this information into a table that is correctly positioned. Thank you in advance. If you need to see any more of my code, then just let me know. If I am unclear, please let me know what I should explain. Thank you!!!
There are several ways to do this. But from what you already have the simplest is to use innerHTML:
queryContact.find({
success: function(results4) {
var html = "";
alert("Successfully retrieved " + results4.length + " entries.");
// Do something with the returned Parse.Object values
html += '<table border="1" cellspacing="1" cellpadding="5">';
for (var i = 0; i < results4.length; i++) {
var object4 = results4[i];
contactNameArray[i] = object4.get('ContactName');
contactNumberArray[i] = object4.get('ContactNumber');
html += "<tr><td>Number " + i + " is:</td>";
html += "<td>" + contactNameArray[i] + "</td></tr>";
}
html += '</table>';
document.body.innerHTML += html;
},
error: function(error) {
alert("Error: " + error.code + " " + error.message);
}
});
As for centering the table on the page the best way is to use CSS. Unfortunately centering anything in CSS is a bit of a hack. There are several ways to do it. See the answers to this question for all the ways of doing it: How to horizontally center a <div> in another <div>?. Note: scroll through the answers, not just read the top one. There really are a lot of ways to do this and some may not work for you.
A few notes about innerHTML:
Although innerHTML looks like a variable it actually behaves more like a function. Specifically it invokes the HTML compiler of the browser. So if you pass it incomplete tags like:
someDiv.innerHTML += '<table>';
it will see that as an incomplete 'table' tag and deals with it the way the browser usually does when it sees an incomplete 'table' tag. For some browsers that means removing the table from the DOM. For others that means immediately inserting a closing </table> tag to make it valid. What this means is that when you later append the closing tag like this:
someDiv.innerHTML += '</table>';
what happens is that the browser will think you did this:
<table></table></table>
^ ^
| |_________ what you're trying to insert
|
auto inserted by the browser earlier
and deal with it the way browsers usually do - consider that tag invalid and discard it.
So you need to pass innerHTML well-formed html which is why I created the table structure in a string then append it to the DOM with innerHTML.
A lot of people consider innerHTML stylistically bad since you're doing DOM manipulation with strings. Also because innerHTML was not originally part of any standard and was a proprietary feature of IE. Since it's not part of any standard there's no real agreement between different browsers for how it should work. Having said that, it's probably the most cross-bowser compatible method of manipulating DOM because it's the most widely implemented (even on really old browsers).
Read the documentation of the DOM API for more info on how to do it "properly": https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model
As mentioned by others in the comment to your question, there are also libraries out there that would make your life easier for DOM manipulation. There are procedural libraries like jQuery that wraps the often clunky DOM API and cross-browser issues into a nice to use API. There are also templating library like handlebars that allows you to write the HTML fragments as templates to be processed and inserted into the DOM.
I suggest getting comfortable with how the DOM works by reading the DOM documentation and follow some tutorial and also look at DOM manipulation libraries like jQuery or YUI or underscore to get a better feel of javascript.
Paraphrasing Douglas Crockford, you wouldn't start to program in C or Java without learning the language first. Similarly javascript is a full-featured language. Take some time to learn it and not just assume that "it works like [insert a language you know]". There are many features of javascript like closures and asynchronous functions that will trip you up. Similarly the DOM has many behaviors that may work differently from your assumptions of how HTML works.
But it is a lot to take in. So for now use innerHTML with care being aware of its limitations. Also look at document.getElementById() which is the second most used DOM API for beginners. It allows you to insert your html anywhere in the document using innerHTML. Not just the end of the document.
From reading your questions I deduced the following...
How can I center my table using javascript.
I am having an issue getting my information into a table.
Typically it is not a good idea to do styling within your javascript. While it may seem nice to handle such things conveniently within your jscript, it can end up blowing up your code if not used with moderation. Your best bet would be to write some css, perhaps a generic class that can center an element to a page, and then apply this class to the table element. No Javascript needed, and it makes your code more modular to boot!
Here is a hacky bit of centering code that has worked for me to center a registration form div (Height and Width can be adjusted however you like, use of pixels is not a must.):
body > #register {
margin: auto;
position: absolute;
text-align: center;
height: 156px;
width: 160px;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
For the issues you are experiencing adding information to your table, without knowing what errors or exact output your are seeing, all I can do is go off what I can see in the code. Your generate table function has a few areas I noticed....
The function creates a table, sets an id to it, and appends it to the document, it then creates a new row, setting an id to it as well, it then appends the new row to the table. Then a cell is created.
Here is where I see a problem...
You then jump into a for loop limited by the length of query4 (I'll assume this is the query containing your contact info) and create text nodes, appending them to z (the cell) each iteration, if I am not mistaken that would actually result in the cell in the first(and only) row getting blown up with all your query info. What should be happening is the for loop adds the name and number to its own cells in a NEW row each iteration. This would be your psuedocode...
Create table
Start for loop over contact info for each item...
Create new row
Create new cells
add info to respective cells
append cells to row
append row to table
rinse and repeat
Based on what you have, here is a rough untested representation of what I am suggesting, I built it out of your own code, but it could be done in several ways really...
function generate_table() {
// Create our table
var table = document.createElement("TABLE");
document.body.appendChild(table);
for(var i = 0; i < query4.length; i++) {
// Set up an awesome new row.
var row = document.createElement("TR");
// Set up awesome new cells.
var nameCell = document.createElement("TD");
var numberCell = document.createElement("TD");
// Instantiate variables to hold our data.
var name = document.createTextNode(contactNameArray[i]);
var number = document.createTextNode(contactNumberArray[i]);
// Add values to cells
nameCell.appendChild(name);
numberCell.appendChild(number);
// Add cells to row.
row.appendChild(name);
row.appendChild(number);
// Build out awesome row.
table.appendChild(row);
}
}
I did a couple things here, first off some variable renaming, descriptive variable names do wonders for code readability and maintenance later on. (Check out the book "Clean Code", it talks on this at length, it changed the way I look at code virtually overnight.)
One other thing, I assume the query4 variable is being set up in the global scope, that will work, but it's typically good to try an keep the global space clear if and when you can. (See Douglas Crockfords "Javascript: The Good Parts", another great book on Javascript that really helped me learn the language.) Maybe consider passing the data to the generate table function, and calling the function in the callback of the parse data return?
Anyway, that is my "brief" two cents, hope it helps!
Good luck.

Avoid double looping to insert nodes and activate them?

A common problem I have is the need to create multiple DOM nodes in a loop, and then activate those nodes in some way, either by applying a plugin, an event handler or similar. The activation step requires that the element actually exist first.
So you end up doing something like:
// Loop 1: Create the nodes
var HTML = '<tr id="UID">';
for(var k in Fields){ // Fields is an object!
HTML += '<td>';
HTML += '<input class="ActivateMe"/>';
HTML += '</td>';
}
var HTML += '</tr>';
$TableBody.children('tr').first().before(HTML);
// Loop 2: Activate the new nodes
$('#'+UID).children('td').children('.ActivateMe').each(function(index){
$(this).InitSomePlugin();
});
The code above is simplified for the question, but assume that each element inside a given cell can be different (maybe an input, may be a div), and might also require a different plugin (Maybe it's a color picker, maybe it's a combo box).
Is it possible to avoid looping over the data set twice and doing the insert and activate in one go? I think it may be possible by appending the nodes within the first loop, which would also allow activation in the first loop. But it is generally considered bad practice to use append in a loop rather than store all your HTML it in a var and append all the HTML at once. At the same time, looping over the same set of data two times seems inefficient too. What is the best way to handle this scenario with minimal performance impact?
Yes. Don't build a lengthy HTML string, but create the elements programmatically in the first loop so that you can direclty instantiate your plugin on them:
var $TableBody = …,
var $row = $('<tr>', {id:UID});
for(var k in Fields) { // sure that Fields is an object?
// For an array, use a normal for loop
var $cell = $('<td>');
var $input = $('<input class="ActivateMe"/>');
$input.InitSomePlugin();
$input.appendTo($cell);
$cell.appendTo($row);
}
$row.prependTo($TableBody);
You might need to do the appends before calling .InitSomePlugin(). You also might want to nest the calls and use chaining for shortening the code:
var $row = $('<tr>', {id:UID}).prependTo(…);
for(var k in Fields)
$('<input class="ActivateMe"/>')
.appendTo($('<td>').appendTo($row))
.InitSomePlugin();
$tableBody = $('table');
// Prepare an DOM object but don't append it to DOM yet.
$tr = $('<tr></tr>',{
id: "UID"
});
// Loop over any condition you would see fit.
for(var i = 0; i < 2; ++i) {
$td = $('<td></td>',{
// Prepare your input element with your event bound to it
"html": $('<input/>', {"class": "ActivateMe"}).InitSomePlugin();
});
// append your td element
$tr.append($td);
}
// Add your tr element to the beginning of your table with prepand method.
$tableBody.prepend($tr);
Since the activation step requires that the element already exists and (probably) is in the DOM, then you only have two choices here:
1) You can create each DOM element individually (with things like document.creatElement() or jQuery's $(html)) such that you have saved DOM object references that you can later use for intializing the plugin.
or
2) You can build up a string of HTML as you are doing. Insert that string, letting the browser create all the elements for you and then you will have to find the appropriate DOM elements in order to initialize the plugins.
Tests have shown that it is often the case that browsers will create lots of HTML objects faster when given a string of HTML rather than manually creating and inserting individual DOM objects so there is no particular issue with using the string of HTML.
There is no 100% right or wrong answer here. Performance is probably not the primary issue unless you have hundreds to thousands of these DOM elements. I tend to go with whichever path leads to the cleanest and simplest code.
In your case, you have to iterate over the Fields object so you can't avoid that. You have to find the first row of your table so you can't avoid that.
If you've decided that building the string of HTML is the most expedient approach to writing the code (which it probably is here), then you can't avoid refinding the objects you need to activate in the DOM.
You can be as efficient about things as possible.
Here's a little bit of streamlining that stays with the basic philosophy:
// create the new rows
var HTML = '<tr id="UID">';
for(var k in Fields) {
HTML += '<td>' + '<input class="ActivateMe"/>'+ '</td>';
}
HTML += '</tr>';
// create an insert new content, save reference to new content
var newObj = $(HTML);
$TableBody.prepend(newObj);
// now activate the plugin on the appropriate objects in the new content
newObj.find(".ActivateMe").InitSomePlugin();
Streamlining steps:
Create each cell in one statement rather than three
When you create the new row object, keep a reference to it so we don't have to find it again.
When you add the new content, use .prepend() to make it the first row rather than finding all the rows and selecting the first one
With you initalize the plugin, there's no need for a .each() loop if you're running the same jQuery method on every object. You can so it like this: newObj.find(".ActivateMe").InitSomePlugin(); without .each().
You could also create the DOM objects yourself and not use the HTML string and keep track of the objects that need to be activate as you go so they don't have to be found again:
// create the new rows
var row = $('<tr id="UID"></tr>'), input, item, activates = [];
for(var k in Fields) {
item = $('<td>');
input = $('<input class="ActivateMe"/>')
activates.push(input);
item.append(input);
row.append(item);
}
// insert row into table
$TableBody.prepend(row);
// now activate the plugin on the appropriate objects that are now inserted
$(activates).InitSomePlugin();
Purists might "like" the second option better than the first option because it's not using an HTML string, but unless you're doing this hundreds to thousands of times such that performance is paramount (in which case you'd have to test which method actually performs better and diagnose why), I can't honestly say that the second is better than the first. I like the coding simplicity of the first and finding a few class objects in a specific table just isn't an expensive operation.

Using jQuery append for a large list of select options

I'm using javascript to duplicate a couple select lists and append them to another element in the DOM. One of these lists is very large: 29843 options. I've read a lot of tips on improving the performance of jQuery's append method, including only appending once and using an array instead of concatenating. I think the code below doesn't violate any of these practices, but when I call the append method, the browser (even Chrome) hangs.
var rows = $('.runRow');
var envOpts = rows[0].children[1].children[0].innerHTML; // contains 3 <option> tags
var sumOpts = rows[0].children[2].children[0].innerHTML; // contains 29843 <option> tags
var newRow = "<span class=\"runRow\">";
newRow += "<span>Run " + (numRuns++) + "</span>";
newRow += "<span><select id=\"Environment" + numRuns + "\" name=\"Environment" + numRuns + "\">";
newRow += envOpts;
newRow += "</select></span>";
newRow += "<span><select id=\"SummaryID" + numRuns + "\" name=\"SummaryID" + numRuns + "\">";
newRow += sumOpts;
newRow += "</select></span>";
newRow += "</span>";
$('#RunsTable').append(newRow);
Am I asking too much of javascript, or is there a way to perform this operation?
Thanks!
You are asking too much. Not necessarily of jQuery, though, or even JavaScript. As James Montagne's comment suggests, your (ab)use of the idea of a dropdown menu might be outide the bounds of what the operating system/windowing framework performs well with.
Have you considered a hierarchical approach, with the 30k options categorized and then presented to the user as a list of dropdowns?
Okay, here is a means that is faster than what you are currently trying, but still pretty slow. This actually uses javascript to both create and display the 30k options so we are really working the system here. It takes about sixty seconds to render on my two year old laptop. You start by using array.push and join which offer significant performance improvements over += string.
http://jsfiddle.net/yuTtK/1/ - Link defaults to 300 as not to swamp someone's browser.
//create an option list with 30k options
var options = [];
for (var i = 0; i< 30000; i++) {
options.push('<option>'+i+'</option>');
}
$('select').append(options.join(''));
//copy each select list item onto the page
var myArray = [];
var list = $('option');
for (var j=0; j < list.length; j++) {
myArray.push('<span>'+list[j].innerHTML+'</span><br/>');
}
$('body').append(myArray.join(''));
To make this more user friendly you can use a timeout and a loading message so that the browser is responsive while it iterates over the list.
I suggest you to divide options into groups and having them depending one from the others if possible. But why the hell you need 28000(!) options???
I would agree with Paul and Dan on this one. Another option which I particularly like implementing for huge lists is autocomplete. Making an ajax call to grab the top ten or so choices based on keystrokes is a lot more efficient than trying to pull in 30K.

Script performace - script crashing the browser

I have a script that call a CGI to retrieve an XML, parses it, creates a table and show this table to the user. It is pretty simple..
My problem is when this XML is too big because it sometimes crashes the user's browser.
So I want your opinion about what I can improve in my function.
The function does that:
[1]. It parses the XML:
var xmlDoc = req.responseXML;
var rows = xmlDoc.getElementsByTagName('row');
var columns = rows[0].getElementsByTagName('column');
And after this I iterate over the columns to create my table.
[2]. I'm creating the table concatenating strings, like this (iterating over each column in each row):
tableCells += '<td style="text-align:left">' + value + '</td>';
[3]. And to finish, I do this:
document.getElementById('results').innerHTML = resultsTable;
document.getElementById('results').style.display = "";
I checked how time takes these steps (profiling with chrome and firefox+firebug):
I removed all the concatenation and the [3]. The funcion took 0.5s.
I removed only the [3]. The function took 1.5s.
But if I add the [3] my function takes 15.5s (!!!).
What can I do to improve it?
Thank you!
Since innerHTML needs to be parsed by the browser you could try to speed things up by inserting DOM objects, so the there is no need of parsing a large string.
Instead of adding strings, you could use something like this in your table:
var resultTable = document.getElementById('...'), newRow, newCell;
// ... begin loop:
newRow = document.createElement('tr');
newCell = document.createElement('td');
newCell.textContent = "abc"; // use innerText in IE
newRow.appendChild(newCell);
resultTable.appendChild(newRow);
if you want to stick to strings, or if this does not give you a lot of improvement, you should optimize your HTML, for instance the style in here: <td style="text-align:left"> can be eliminated by using CSS and should save you a lot of bytes which do not need to be parsed anymore. Add a CSS like this to acheive this: td {text-align: left;}
Not sure if may help but:
Create an element using javascript (Ex.:var el = document.createElement('span'))
Set the element innerHTML (Ex.: el.innerHTML = resultsTable)
appends it to the page

Most efficient way to create form with Jquery based on server data

I have a database webmethod that I call via Jquery ajax. This returns an array of data objects from the server. For each data object I want to populate a form with maybe a couple dozen fields.
What is the most efficient way to generate and populate these forms.
Right now, I create a string of the html in my javascript code for each record and then add it to the page. Then I add some events to the new elements.
This works OK in firefox, about 700 ms total for an array of 6 elements, and 4500 ms for an array of 30 elements. However, this is an internal app for my company, and the clients can only use IE8 on their machines. In IE8, this takes 2-10 SECONDS! for and array of length 6 and 47 seconds the one time it was actually able to complete for an array of length 30. Not sure what the ##$% IE8 is doing, but anyways... I need something more efficient it seems...
Thanks!
MORE INFO:
Edit: first thing I do is:
$("#approvalContainer").empty();
Web method signature:
[WebMethod]
public List<User> ContractorApprovals()
So I call the method and then do this with the data:
for (var i = 0; i < data.length; i++) {
displayUserResult("#approvalContainer", data[i], false);
}
formEvents("#approvalContainer");
$("#approvalContainer").show("slow");
displayUserResult looks like this:
var div = "<div style=\"width:695px\" class=..........."
fillForm(div,data)
$("#approvalContainer").append(div)
formEvents does things like add click events, create styles buttons and add watermarks.
fillForm does stuff like this:
$(userForm).find(".form-web-userName").text(userObject._web._userName);
where userForm is the div created in the displayUserResult function, and userObject is one of the objects in the array returned by the ajax call.
Please let me know if you need more information.
You are doing too much DOM manipulation. Every .append and .find and .text inside your loop makes it slower.
The most efficient way is to build an entire HTML string and append that once. I'm not going to bother with the displayUserResult function, and just modify your function that handles the data:
var div = "<div style=\"width:695px\" class=...........",
html = "";
for (var i = 0; i < data.length; i++) {
// Keep adding on to the same html string
html += div + data[i]._web._userName + "</div>";
}
// After the loop, replace the entire HTML contents of the container in one go:
$("#approvalContainer").html(html);
However, while this is fast, note that this is only appropriate if _userName doesn't contain special characters, or is already HTML escaped.

Categories

Resources