I am creating a large table dynamically using Javascript.
I have realised the time taken to add a new row grows exponentially as the number of rows increase.
I suspect the Page is getting refreshed in each loop per row (I am also adding input elements of type text on each cell)
Is there a way to stop the page "refreshing" until I am done with the whole table?
Any suggestion on how to do this or go around it?
I have tried all the suggestions above, but I am still getting the performance bottlenecks.
I tried to analyse line after line, and I have noted that document.getElementById() is one of the lines taking a lot of time to execute when the table is very large.
I am using getElementById() to dynamically access an HTML input of type text loaded on each cell of the table.
Any ideas on which DOM method I should use instead of getElementById()?
You can create the table object without adding it to the document tree, add all the rows and then append the table object to the document tree.
var theTable = document.createElement("table");
// ...
// add all the rows to theTable
// ...
document.body.appendChild(theTable);
Maybe build your table as a big string of HTML, and then set the .innerHTML of a container div to that string when you've finished?
Did you try the <tbody> tag when you create the table?
It is possible browsers optimize that and don't "refresh" the table while populating it.
If your cells sizes are the same for each row then you will be able to specify style table-layout:fixed - this will give you the greatest performance gain as the browser won't have to recalculate cells sizes each time a row is added
Use the table DOM to loop trough the rows and cells to populate them, instead of using document.getElementByID() to get each individual cell.
E.g.
thisTable = document.getElementByID('mytable');//get the table
oneRow = thisTable.rows[0].cells; //for instance the first row
for (var colCount = 0; colCount <totalCols; colCount ++)
{
oneCell =oneRow[colCount];
oneCell.childNodes[0].value = 'test';//if your cell contains an input element
oneCell.innerHTML = 'test'; // simply write on the cell directly
}
Hope that helps someone else...
Cheers.
Related
I'd like to use Javascript to set up the display logic for individual rows in a single-choice matrix table question in Qualtrics. I've always done this in the past with the usual click-through method, but I often have 100+ rows to do this for, and it'd save a ton of time to be able to do this programmatically.
I've tried inserting the following into "Add JavaScript" for the question I'm trying to add the display logic to:
Qualtrics.SurveyEngine.addOnload(function()
{
/*Set display logic*/
if ('${q://QID2/SelectedAnswerRecode/1}' < 3) {'${q://QID3/ChoiceDescription/1}'.style.visibility='hidden';}
if ('${q://QID2/SelectedAnswerRecode/2}' < 3) {'${q://QID3/ChoiceDescription/2}'.style.visibility='hidden';}
});
The idea is that an answer with a value of at least 3 in row 1 of QID2 (also a single-choice matrix table) is required for row 1 of matrix table QID3 to appear, and so on. As it is, is appears unresponsive - rows in QID3 are still displayed even if selected values in the corresponding rows of QID2 are < 3.
I've also tried style.display='none' instead of style.visibility='hidden' with no success. My experience with Javascript is limited, so I suspect it's some kind of syntax issue.
The problem is indeed with your syntax. You need to hide an html element and '${q://QID3/ChoiceDescription/1}' is not an html element (it is the innerHTML of a label). Even if it were an html element, the syntax is wrong (it wouldn't be in quotes).
It is best to use prototypejs when possible, so if the element were named 'element' the command would be:
element.hide();
To find the correct elements to hide, you need to identify and find them by element id or some combination of element tag, class and attribute. It could be done using choice description, but it would take a lot more code and wouldn't be very efficient.
Use Inspect Element in the browser with the survey in Preview mode to find the element ID for the row header of the matrix where the display logic will occur. .up().hide() will grab the rest of the row as well. Quotes around the IDs are necessary because of the '~' in the ID names; otherwise I get an "unexpected token ~" error when trying to save it. Thanks to T. Gibbons for pointing me in the right direction.
Qualtrics.SurveyEngine.addOnload(function()
{
/*Set display logic*/
if ('${q://QID2/SelectedAnswerRecode/1}' < 3) {$('header~QID3~1').up().hide();}
if ('${q://QID2/SelectedAnswerRecode/2}' < 3) {$('header~QID3~2').up().hide();}
if ('${q://QID2/SelectedAnswerRecode/3}' < 3) {$('header~QID3~3').up().hide();}
if ('${q://QID2/SelectedAnswerRecode/4}' < 3) {$('header~QID3~4').up().hide();}
if ('${q://QID2/SelectedAnswerRecode/5}' < 3) {$('header~QID3~5').up().hide();}
});
Copy/paste/edit as needed.
UPDATE
Looks like things may have changed with updates, but while this works with the question itself, this can mess with the logic on subsequent questions. Use extreme caution!
I have a big table, here is a section of it:
My goal is to make each section collapsible and uncollapsible by toggling all section rows apart from the Sum row and moving the section heading to the empty sum row cell, then moving it back to its original form when it is next toggled.
I have tried to approach this from a few angles but as I'm a jQuery novice, I can't for the life of me find an efficient way to do it.
The first part of the problem is actually defining the sections. As they're of varying lengths long, I figured I'd first need to cycle through each row and check if the first cell is empty or not. If it isn't, I assume it's a new section, if it is, I assume it's another and add it to the current section.
Then for each section's first row and cell I set an on click event that toggles all rows apart from the Sum row and moves the section title into it.
I have managed to accomplish this but it uses ungodly amounts of fugly Javascript and is very slow. So like I said, I'm wondering how I can do this in the most efficient way possible.
In essence, is there a nice way I can select all sections using just jQuery selectors instead of having to loop through every single row and inspect each cell etc? I'm happy just to know if there's a more efficient way to do it or not, but a solution would be much appreciated.
For the sake of curiosity, here is the code I used to colour the image:
var rowCount = $("tr").length;
var rows = $("tr > td:first-child").not("tr:nth-child(1) > td:first-child").not("tr:nth-child("+rowCount+") > td:first-child").not("tr:nth-child("+(rowCount-1)+") > td:first-child");
var sectionTitles = $(rows).filter(function(){
return !($(this).text()=="");
});
var sectionItemTitles = $(rows).not(sectionTitles);
sectionTitles.css({"background":"red"});
sectionTitles.attr("type","section");
sectionItemTitles.css({"background":"blue"});
sectionItemTitles.attr("type","row");
To achieve this functionality you will have to identify the rows which need to be grouped togetherand add a class and an image of plusminus to indicate collapsible space. On the click function of the plusminus, use the toggle() function to collapse or uncollapse the grouped data.
toggle jquery
I am using JQuery to manipulate a page which I do not have access to modify server-side. One of the main things I need to do is create a copy of just a sub-section of one of the tables on the page. More specifically, I need to create 2 tables, one which is a copy of just the first two rows of the table and another which is just the first column.
After optimizing it as far as I could, I'm fairly happy with the performance of the row copy but the column copy is still a bit on the slow side.
I have the following, where sourceTable is the original table and titleColBody is the tbody of a new table created earlier:
sourceTable.find("tr").each(function(){
titleColBody.append(
$("<tr></tr>").append($(this).children(":first").clone())
);
});
On a table with roughly 50 rows and a huge number of columns, this takes approx. 350ms in IE8. Not terrible, but I would like to bring this down a little if possible.
My question is if there is a more efficient way of doing this in JQuery.
Similarly, I create the copy of the first 2 rows of the table as below.
headerTable.find("tbody")
.append(sourceTable.find("tr:eq(0)").clone())
.append(sourceTable.find("tr:eq(1)").clone());
This is fairly quick, however if there is a better way of doing it I would be happy to hear it.
Any optimization at all would be greatly appreciated.
Seems like this might be a tad bit faster for the columns, this way you'll only grab one resource set instead of grabbing two sets (one for the TRs and then one for the child TDs):
sourceTable.find("tr > td:first-child").each(function(){
titleColBody.append($("<tr></tr>").append($(this).clone()));
});
Also on another note, if the "titleColBody" is a pointer to a DOM object that causes the browser to have to render the page when it's updated, you're better off appending to a documentFragment and then appending that fragment (so the browser only re-renders once).
For instance:
var titleCol = document.createDocumentFragment();
sourceTable.find("tr > td:first-child").each(function(){
titleCol.append($("<tr></tr>").append($(this).clone()));
});
titleColBody.append(titleCol);
im having a firefox issue where i dont see the wood for the trees
using ajax i get html source from a php script
this html code contains a tag and within the tbody some more tr/td's
now i want to append this tbody plaincode to an existing table. but there is one more condition: the table is part of a form and thus contains checkboxe's and drop down's. if i would use table.innerHTML += content; firefox reloads the table and reset's all elements within it which isnt very userfriendly as id like to have
what i have is this
// content equals transport.responseText from ajax request
function appendToTable(content){
var wrapper = document.createElement('table');
wrapper.innerHTML = content;
wrapper.setAttribute('id', 'wrappid');
wrapper.style.display = 'none';
document.body.appendChild(wrapper);
// get the parsed element - well it should be
wrapper = document.getElementById('wrappid');
// the destination table
table = document.getElementById('tableid');
// firebug prints a table element - seems right
console.log(wrapper);
// firebug prints the content ive inserted - seems right
console.log(wrapper.innerHTML);
var i = 0;
// childNodes is iterated 2 times, both are textnode's
// the second one seems to be a simple '\n'
for(i=0;i<wrapper.childNodes.length;i++){
// firebug prints 'undefined' - wth!??
console.log(wrapper.childNodes[i].innerHTML);
// firebug prints a textnode element - <TextNode textContent=" ">
console.log(wrapper.childNodes[i]);
table.appendChild(wrapper.childNodes[i]);
}
// WEIRD: firebug has no problems showing the 'wrappid' table and its contents in the html view - which seems there are the elements i want and not textelements
}
either this is so trivial that i dont see the problem OR
its a corner case and i hope someone here has that much of expirience to give an advice on this - anyone can imagine why i get textnodes and not the finally parsed dom elements i expect?
btw: btw i cant give a full example cause i cant write a smaller non working piece of code
its one of those bugs that occure in the wild and not in my testset
thx all
You are probably running into a Firefox quirk of following the W3C spec. In the spec the whitespace between tags are "text" nodes instead of elements. These TextNodes are returned in childNodes. This other answer describes a workaround. Also Using something like JQuery makes this much easier.
I would expect this behavior in any browser as the += operand overwrites what is already in the table by definition. Two solutions:
Instead of receiving HTML code from your PHP file, have the PHP generate a list of items to add to the table. Comma/tab separated, whatever. Then use Table.addRow(), Row.addCell() and cell.innerHTML to add the items to the table. This is the way I would suggest doing it, no point in creating GUI elements in two separate files.
The other solution is to save all the form data that's already been entered to local JavaScript variables, append the table, and then reload the data into the form fields.
Well, returning a JSON object with the new data seems like the best option. Then, you can synthesize the extra table elements by using it.
In case one is forced to get plain HTML as response, it is possible to use var foo = document.createElement('div');, for example, and then do foo.innerHTML = responseText;. This creates an element that is not appended to anything, yet hosts the parsed HTML response.
Then, you can drill down the foo element, get the elements that you need and append them to the table in a DOM-friendly fashion.
Edit:
Well, I think I see your point now.
wrapper is a table element itself. The nodes reside under the tbody, a child of wrapper which is its lastChild (or you can access it via its tBodies[0] member, in Firefox).
Then, using the tBody element, I think that you would be able to get what you want.
BTW, You do not need to append the wrapper to the document before appending its children to the table, so no need to hide it etc.
What is the best plain javascript way of inserting X rows into a table in IE.
The table html looks like this:
<table><tbody id='tb'><tr><td>1</td><td>2</td></tr></tbody></table>
What I need to do, is drop the old body, and insert a new one with 1000 rows. I have my 1000 rows as a javascript string variable.
The problem is that table in IE has no innerHTML function. I've seen lots of hacks to do it, but I want to see your best one.
Note: using jquery or any other framework does not count.
Here's a great article by the guy who implemented IE's innerHTML= on how he got IE to do tbody.innerHTML="<tr>...":
At first, I thought that IE was not
capable of performing the redraw for
modified tables with innerHTML, but
then I remembered that I was
responsible for this limitation!
Incidentally the trick he uses is basically how all the frameworks do it for table/tbody elements.
Edit: #mkoryak, your comment tells me you have zero imagination and don't deserve an answer. But I'll humor you anyway. Your points:
> he is not inserting what i need
Wha? He is inserting rows (that he has as an html string) into a table element.
> he also uses an extra hidden element
The point of that element was to illustrate that all IE needs is a "context". You could use an element created on the fly instead (document.createElement('div')).
> and also the article is old
I'm never helping you again ;)
But seriously, if you want to see how others have implemented it, take a look at the jQuery source for jQuery.clean(), or Prototype's Element._insertionTranslations.
Do as jQuery does it, eg. add <table> and </table> around it, insert into document and move the nodes you want to where you want them and ditch the temp-element.
the code ended up being this:
if($.support.scriptEval){
//browser needs to support evaluating scripts as they are inserted into document
var temp = document.createElement('div');
temp.innerHTML = "<table><tbody id='"+bodyId +"'>"+html;
var tb = $body[0];
tb.parentNode.replaceChild(temp.firstChild.firstChild, tb);
temp = null;
$body= $("#" + bodyId);
} else {
//this way manually evaluates each inserted script
$body.html(html);
}
Things that beed to exist beforehand: a table that has a body with id of 'bodyId'. $body is a global variable (or the function has a closure on it), and there is a bit of jquery in there too, because IE does not evalute scripts that are inserted into the html on the fly.
I had the same problem (as do lots of other people) and after a lot of playing around here's what I got to work.
You have to make a tr via document.createelement ('tr') then make a td, the same way.
appendchild the td to the tr, appendchild the tr to tbody (not table) THEN you can innerhtml the td you created and it will work.
This was ie8 I was using. Basically the table structure has to be made with createelement but the rest of it can be innerHTMLed. I was doing this watching in the IE8 debugger and it would say it would add it (if I did tr.innerhtml="blah") and give no error, but it wouldn't display, and the html dom view showed a very broken table (no td ever showed up, but the /td did)
So when finally I did the tr AND td by createelement calls, it created a correct looking dom and drew the page correctly.