Javascript table creation - javascript

Trying to create a table using the following code but not working. Please point out where I'm going wrong.
var i,j;
function cell(ih){
var tcell =document.createElement('td');
tcell.innerHTML=ih;
return tcell;
}
mutable=document.createElement('table');
for (i=0;i<10;i++){
row1=document.createElement('tr');
for(j=0;j<10;j++){
row1.appendChild(cell(j));
}
mutable.appendChild(row1);
document.write(mutable);
}

You have several problems, the first two are the big ones, the second two are a matter of style and risk of clashes with other code:
You are trying to document.write HTMLElementNodes. document.write only deals with strings. Grab a container element (e.g. with document.getElementById) and append to it
You are trying to document.write the entire table every time you add a row to it. Append the table once the table is complete, not every time you go through the loop.
You are using globals all over the place, learn to love the var keyword
row1 is a poor variable name for the row you are operating on which usually isn't the first

Use document.body.appendChild(...) instead of document.write(...).

You can do it by changing your script to use document.body.appendChild(mutable) after your nested for loop:
var i,j;
function cell(ih){
var tcell =document.createElement('td');
tcell.innerHTML=ih;
return tcell;
}
mutable=document.createElement('table');
for (i=0;i<10;i++){
row1=document.createElement('tr');
for(j=0;j<10;j++){
row1.appendChild(cell(j));
}
mutable.appendChild(row1);
}
document.body.appendChild(mutable);
This appends the entire mutable table object you've created to the <body> element of your page. You can see it working here.

Also note that most times in markup, you don't see the <tbody> element, but it is good practice to append this as a child element of the <table> and as a parent element for all of your rows. So, your script should look more like this:
function cell(ih){
var tcell = document.createElement('td');
tcell.innerHTML = ih; // I would suggest you use document.createTextNode(ih) instead
return tcell;
}
function appendTable() { // you now have to call this function some time
mutable = document.createElement("table");
var tBody = mutable.appendChild( document.createElement("tbody") ); // technique using "fluid interfaces"
for (var i = 0; i < 10; i++) {
var row1 = tBody.appendChild( document.createElement('tr') ); // fluid interface call again
for(var j = 0; j < 10; j++) {
row1.appendChild(cell(j));
}
}
document.body.appendChild(mutable);
}
I made some style changes to your script, and I would suggest making even more, but as far as correctness, it should work.

Related

Javascript performance optimization

I created the following js function
function csvDecode(csvRecordsList)
{
var cel;
var chk;
var chkACB;
var chkAF;
var chkAMR;
var chkAN;
var csvField;
var csvFieldLen;
var csvFieldsList;
var csvRow;
var csvRowLen = csvRecordsList.length;
var frag = document.createDocumentFragment();
var injectFragInTbody = function () {tblbody.replaceChild(frag, tblbody.firstElementChild);};
var isFirstRec;
var len;
var newEmbtyRow;
var objCells;
var parReEx = new RegExp(myCsvParag, 'ig');
var tblbody;
var tblCount = 0;
var tgtTblBodyID;
for (csvRow = 0; csvRow < csvRowLen; csvRow++)
{
if (csvRecordsList[csvRow].startsWith(myTBodySep))
{
if (frag.childElementCount > 0)
{
injectFragInTbody();
}
tgtTblBodyID = csvRecordsList[csvRow].split(myTBodySep)[1];
newEmbtyRow = getNewEmptyRow(tgtTblBodyID);
objCells = newEmbtyRow.cells;
len = newEmbtyRow.querySelectorAll('input')[0].parentNode.cellIndex; // Finds the cell index where is placed the first input (Check-box or button)
tblbody = getElById(tgtTblBodyID);
chkAF = toBool(tblbody.dataset.acceptfiles);
chkACB = toBool(tblbody.dataset.acceptcheckboxes) ;
chkAN = toBool(tblbody.dataset.acceptmultiplerows) ;
tblCount++;
continue;
}
csvRecordsList[csvRow] = csvRecordsList[csvRow].replace(parReEx, myInnerHTMLParag); // Replaces all the paragraph symbols ΒΆ used into the db.csv file with the tag <br> needed into the HTML content of table cells, this way will be possible to use line breaks into table cells
csvFieldsList = csvRecordsList[csvRow].split(myEndOfFld);
csvFieldLen = csvFieldsList.length;
for (csvField = 0; csvField < csvFieldLen; csvField++)
{
cel = chkAN ? csvField + 1 : csvField;
if (chkAF && cel === 1) {objCells[cel].innerHTML = makeFileLink(csvFieldsList[csvField]);}
else if (chkACB && cel === len) {objCells[cel].firstChild.checked = toBool(csvFieldsList[csvField]);}
else {objCells[cel].innerHTML = csvFieldsList[csvField];}
}
frag.appendChild(newEmbtyRow.cloneNode(true));
}
injectFragInTbody();
var recNum = getElById(tgtTblBodyID).childElementCount;
customizeHtmlTitle();
return csvRow - tblCount + ' (di cui '+ recNum + ' record di documenti)';
}
More than 90% of records could contain file names that have to be processed by the following makeFileLink function:
function makeFileLink(fname)
{
return ['<a href="', dirDocSan, fname, '" target="', previewWinName, '" title="Apri il file allegato: ', fname, '" >', fname, '</a>'].join('');
}
It aims to decode a record list from a special type of *.db.csv file (= a comma-separated values where commas are replaced by another symbol I hard-coded into the var myEndOfFld). (This special type of *.db.csv is created by another function I wrote and it is just a "text" file).
The record list to decode and append to HTML tables is passed to the function with its lone parameter: (csvRecordsList).
Into the csv file is hosted data coming from more HTML tables.
Tables are different for number of rows and columns and for some other contained data type (which could be filenames, numbers, string, dates, checkbox values).
Some tables could be just 1 row, others accept more rows.
A row of data has the following basic structure:
data field content 1|data field content 2|data field content 3|etc...
Once decoded by my algorithm it will be rendered correctly into the HTML td element even if into a field there are more paragraphs. In fact the tag will be added where is needed by the code:
csvRecordsList[csvRow].replace(par, myInnerHTMLParag)
that replaces all the char I choose to represent the paragraph symbol I have hard-coded into the variable myCsvParag.
Isn't possible to know at programming time the number of records to load in each table nor the number of records loaded from the CSV file, nor the number of fields of each record or what table field is going to contain data or will be empty: in the same record some fields could contain data others could be empty. Everything has to be discovered at runtime.
Into the special csv file each table is separated from the next by a row witch contains just a string with the following pattern: myTBodySep = tablebodyid where myTBodySep = "targettbodydatatable" that is just a hard coded string of my choice.
tablebodyid is just a placeholder that contains a string representing the id of the target table tbody element to insert new record in, for example: tBodyDataCars, tBodyDataAnimals... etc.
So when the first for loop finds into the csvRecordsList a string staring with the string into the variable myTBodySep it gets the tablebodyid from the same row: this will be the new tbodyid that has to be targeted for injecting next records in it
Each table is archived into the CSV file
The first for loop scan the csv record list from the file and the second for loop prepare what is needed to compile the targeted table with data.
The above code works well but it is a little bit slow: in fact to load into the HTML tables about 300 records from the CSV file it takes a bit more of 2.5 seconds on a computer with 2 GB ram and Pentium core 2 4300 dual-core at 1800 MHz but if I comment the row that update the DOM the function needs less than 0.1 sec. So IMHO the bottle neck is the fragment and DOM manipulating part of the code.
My aim and hope is to optimize the speed of the above code without losing functionalities.
Notice that I'm targeting just modern browsers and I don't care about others and non standards-compliant browsers... I feel sorry for them...
Any suggestions?
Thanks in advance.
Edit 16-02.2018
I don't know if it is useful but lastly I've noticed that if data is loaded from browser sessionstorage the load and rendering time is more or less halved. But strangely it is the exact same function that loads data from both file and sessionstorage.
I don't understand why of this different behavior considering that the data is exactly the same and in both cases is passed to a variable handled by the function itself before starting checking performance timing.
Edit 18.02.2018
Number of rows is variable depending on the target table: from 1 to 1000 (could be even more in particular cases)
Number of columns depending on the target table: from 10 to 18-20
In fact, building the table using DOM manipulations are way slower than simple innerHTML update of the table element.
And if you tried to rewrite your code to prepare a html string and put it into the table's innerHTML you would see a significant performance boost.
Browsers are optimized to parse the text/html which they receive from the server as it's their main purpose. DOM manipulations via JS are secondary, so they are not so optimized.
I've made a simple benchmark for you.
Lets make a table 300x300 and fill 90000 cells with 'A'.
There are two functions.
The first one is a simplified variant of your code which uses DOM methods:
var table = document.querySelector('table tbody');
var cells_in_row = 300, rows_total = 300;
var start = performance.now();
fill_table_1();
console.log('using DOM methods: ' + (performance.now() - start).toFixed(2) + 'ms');
table.innerHTML = '<tbody></tbody>';
function fill_table_1() {
var frag = document.createDocumentFragment();
var injectFragInTbody = function() {
table.replaceChild(frag, table.firstElementChild)
}
var getNewEmptyRow = function() {
var row = table.firstElementChild;
if (!row) {
row = table.insertRow(0);
for (var c = 0; c < cells_in_row; c++) row.insertCell(c);
}
return row.cloneNode(true);
}
for (var r = 0; r < rows_total; r++) {
var new_row = getNewEmptyRow();
var cells = new_row.cells;
for (var c = 0; c < cells_in_row; c++) cells[c].innerHTML = 'A';
frag.appendChild(new_row.cloneNode(true));
}
injectFragInTbody();
return false;
}
<table><tbody></tbody></table>
The second one prepares html string and put it into the table's innerHTML:
var table = document.querySelector('table tbody');
var cells_in_row = 300, rows_total = 300;
var start = performance.now();
fill_table_2();
console.log('setting innerHTML: ' + (performance.now() - start).toFixed(2) + 'ms');
table.innerHTML = '<tbody></tbody>';
function fill_table_2() {// setting innerHTML
var html = '';
for (var r = 0; r < rows_total; r++) {
html += '<tr>';
for (var c = 0; c < cells_in_row; c++) html += '<td>A</td>';
html += '</tr>';
}
table.innerHTML = html;
return false;
}
<table><tbody></tbody></table>
I believe you'll come to some conclusions.
I've got two thoughts for you.
1: If you want to know which parts of your code are (relatively) slow you can do very simple performance testing using the technique described here. I didn't read all of the code sample you gave but you can add those performance tests yourself and check out which operations take more time.
2: What I know of JavaScript and the browser is that changing the DOM is an expensive operation, you don't want to change the DOM too many times. What you can do instead is build up a set of changes and then apply all those changes with one DOM change. This may make your code less nice, but that's often the tradeoff you have when you want to have high performance.
Let me know how this works out for you.
You should start by refactoring your code in multiples functions to make it a bit more readable. Make sure that you are separating DOM manipulation functions from data processing functions. Ideally, create a class and get those variables out of your function, this way you can access them with this.
Then, you should execute each function processing data in a web worker, so you're sure that your UI won't get blocked by the process. You won't be able to access this in a web worker so you will have to limit it to pure "input/output" operations.
You can also use promises instead of homemade callbacks. It makes the code a bit more readable, and honestly easier to debug. You can do some cool stuff like :
this.processThis('hello').then((resultThis) => {
this.processThat(resultThis).then((resultThat) => {
this.displayUI(resultThat);
}, (error) => {
this.errorController.show(error); //processThat error
});
}, (error) => {
this.errorController.show(error); //processThis error
});
Good luck!

variable null, even without being called

I'm attempting to use JavaScript to add rows to a table. I created an anonymous function iterate that iterates id names, and that works fine. The problem is somewhere in my class.
class CreateTable{
constructor(text) {
this.text = text
}
makeTableRow(){
let self = this;
let row = document.createElement('tr');
for (let i = 0; i < 1; i++) {
let el = document.createElement('td');
el.setAttribute('id', iterate(i));
row.appendChild(el);
}
let en = document.getElementById('id1');
console.log(en);
en.innerHTML = self.text;
return row;
}
}
I adapted this class from something that already works, with some tweaks. The en variable is returning a typeError:en is null, and I can't figure it out. What I want to do is create two empty td elements with callable ids, and then add text to the first element immediately. What is the actual problem that is going on here?
The document.getElementById() function returns null if there is no element with the specified ID in the DOM. The elements that you create in the loop have not been added to the DOM, they're appended to a tr that isn't in the DOM either (it's just returned from your function without having been appended to anything).
Just add the required text directly at the time you create the element:
makeTableRow(){
let self = this;
let row = document.createElement('tr');
for (let i = 0; i < 1; i++) {
let el = document.createElement('td');
el.setAttribute('id', iterate(i));
if (i === 0) // for first element
el.innerHTML = self.text; // set the content
row.appendChild(el);
}
return row;
}
Incidentally, you say you want to create two td elements, but your loop only runs for one iteration. The for condition should be i < 2 if you want it to run twice.
Is null because doesn't is identifying the "id", that meaning that you are writing your script before the id="id1".
You need to write your script at the end, just before of "body"
.......
<div id="id1">
</div>
.....
<script>
....
</script>
</body>

Remove all child nodes but the first one

I have an HTML table. The first row contains a checkbox. There is a javascript method associated to the checkbox change. If the checkbox is checked, the code adds a few rows to the table and fills them. If the checkbox is unchecked, the code removes all rows but the first one (the one that contains the checkbox).
The first part of the code works fine : the rows are properly added.
I have an issue with the second part. Here is my code :
if (checkboxValue) {
//Add first row
var tr1 = document.createElement("tr");
var td1_1 = document.createElement("td");
....
tr1.appendChild(td1_1);
var td1_2 = document.createElement("td");
...
tr1.appendChild(td1_2);
table.appendChild(tr1);
//Add second row
var tr2 = document.createElement("tr");
var td2_1 = document.createElement("td");
...
tr2.appendChild(td2_1);
var td2_2 = document.createElement("td");
...
tr2.appendChild(td2_2);
table.appendChild(tr2);
} else {
//Remove all rows but the first
var rows = table.getElementsByTagName("tr");
var nbrRows = rows.length;
if (nbrRows > 1) {
for (var i = 1; i < nbrRows; i++) {
var row = rows[i];
row.parentNode.removeChild(row);
}
}
}
The issue always come from rows[2] being undefined. I have no idea why!
If, instead of using removeChild, I write row.innerHTML = "", I have the visual effect I am looking for (all rows gone), but this is inelegant, since the table now contains several empty rows (their number increasing everytime I check/uncheck the checkbox).
A clue? Thank you very much for your time!
Don't use for-loop to remove DOM elements like this. The problem is that rows is a live collection, meaning that it updates every time you remove elements from DOM. As the result, i counter shifts and eventually you hit "undefined" element spot.
Instead, use while loop. For example, to remove all rows except the first one you could do:
var rows = table.getElementsByTagName("tr");
while (rows.length > 1) {
rows[1].parentNode.removeChild(rows[1]);
}
Also note, that it's getElementsByTagName without s.
UPD. Or iterate backward if you like for-loops better:
var rows = table.getElementsByTagName("tr");
for (var i = rows.length - 1; i > 0; i--) {
rows[i].parentNode.removeChild(rows[i]);
}
Demo: https://jsfiddle.net/9y03co6w/
you remove a row from the array you are iterating over. This is always a bad idea and probably the reason for your error.
solution: start iterating from the end instead of the beginning.
also see for example: example
try to replace this line
var rows = table.getElementsByTagNames("tr");
by
var rows = table.find("tr");

Set HTML table rows all at once

I currently have code that runs through every row of a html table and updates it with a different row.
Here is the code
function sort(index) {
var rows = $table.find('tbody tr');
var a = $table.find('tbody tr');
//Only sort if it has not been sorted yet and the index not the same
if(sortedIndex === index){
for(var i = 0; i < rows.length; i++){
a[i].outerHTML = rows[(rows.length - i)-1].outerHTML;
}
toggleSorted();
}else{
sortedIndex = index;
rows.sort(naturalSort);
for (var i = 0; i < rows.length; i++) {
a[i].outerHTML = rows[i].outerHTML;
}
sortedDown = true;
}
$('#${tableId}').trigger('repaginate');
};
What I am trying to do is to instead of going through every single row in the for loop and setting a[i].outterHTML = rows[i].outterHTML; I would like to just set all of the rows at once. Currently it takes about 1.5 seconds to set them and that is very slow.... Only issue is I cannot seem to find a way to do this. Is this actually possible? (It takes 1.5 seconds on large data sets which is what I am working with).
Since the rows are the same just reordered, you can .append them with the new order:
var $tBody = $table.find('tbody');
var $rows = $tBody.find('tr');
if(sortedIndex === index){
toggleSorted();
$tBody.append($rows.get().reverse());
}
else {
sortedIndex = index;
sortedDown = true;
$tBody.append($rows.get().sort(naturalSort));
}
Here's a fiddle that demonstrates the above: http://jsfiddle.net/k4u45Lnn/1/
Unfortunately the only way to "set all of your rows at once" is to loop through all of your rows and perform an operation on each row. There may be some libraries which have methods and functions that make it look like you're doing the operation on all of your rows at one shot, but ultimately if you want to edit every element in a set you need to iterate through the set and carry out the action on each element, seeing as HTML doesn't really provide any way to logically "link" the attributes of your elements.

How to sort a table knowing row position?

I have a table:
<table>
<tr><td>1</td></tr>
<tr><td>2</td></tr>
<tr><td>3</td></tr>
</table>
An array that tells where every row should come [{index: 2},{index: 1},{index: 0}] (first row is the last from the array, second row is the 1 in array and third row 0 from the array).
Here is my approach.
// create a new temporary tbody outside the DOM (similar to jQuery's detach)
var tbody_tmp = document.createElement('tbody');
// itterate through the array in the order of a new table
for(var i = 0, j = data.length; i < j; i++)
{
// make a copy of current row (otherwise, append child removes the row from the rows array and messes up the index-finder; there got be a better way for this)
var row = rows[data[i].index].cloneNode(true);
tbody_tmp.appendChild(row);
// reset the index to reflect the new table order (optional, outside the sample)
data[i].index = i;
}
// Note that tbody is a jquery object
tbody.parent()[0].replaceChild(tbody_tmp, tbody[0]);
Though, the cloning approach is slow. With 10,000+ records it takes ~1200ms. Furthermore, a jQuery-less approach is preferable.
Posting this in case someone else might find it simple enough for their needs (with less than 1,000 rows).
After hours of restless thinking, I've ended up with the following. If this sample isn't enough, I've written a whole blog post explaining the logic behind it, http://anuary.com/57/sorting-large-tables-with-javascript.
// Will use this to re-attach the tbody object.
var table = tbody.parent();
// Detach the tbody to prevent unnecessary overhead related
// to the browser environment.
var tbody = tbody.detach();
// Convert NodeList into an array.
rows = Array.prototype.slice.call(rows, 0);
var last_row = rows[data[data.length-1].index];
// Knowing the last element in the table, move all the elements behind it
// in the order they appear in the data map
for(var i = 0, j = data.length-1; i < j; i++)
{
tbody[0].insertBefore(rows[data[i].index], last_row);
// Restore the index.
data[i].index = i;
}
// Restore the index.
data[data.length-1].index = data.length-1;
table.append(tbody);

Categories

Resources