creating dynamic grid in javascript - javascript

I'm trying to create a dynamic grid in javascript.
Once I'm able to create the two-dimensional grid I'm going to input variables into my functions so they can accept any numerical inputs. For now, I'm just trying to get the grid the render both vertically and horizontally.
My second function is only allowing me to add cells to the same column instead of appending them horizontally. How can get the new columns to shift horizontally?
Here is my code:
function makeCell() {
const canvas = document.getElementById('pixelCanvas')
const newTable = document.createElement('table')
const tr = document.createElement('tr')
const td = document.createElement('td')
canvas.appendChild(newTable);
newTable.appendChild(tr);
tr.appendChild(td);
}
function makeLine() {
let em = ''
for (let i = 0; i < 2; i++) {
em += makeCell()
}
return em
}
function makeGrid() {
let en = ''
for (let i = 1; i <= 4; i++) {
en += makeLine()
}
return en + '\n'
}
makeGrid();

If you mentally run through the code you'll see where you went wrong in your logic.
MakeGrid fires, you go into the first for loop, makeLine fires, you go into the second inner for loop, makeCell fires, here you select the control with id='pixelCanvas', create a new table, create a new tr and create a new td. Then you append the table to the canvas, the tr to the table and lastly the td to the tr.
That's great for the first cell, but now you want the second... Here's the problem, makeCell completed and we go through round two of the for loop in makeLine. It's the same logic, however, so it creates a new table, but because the id is the same it doesn't overwrite anything. Then, it creates a new tr and a new td and appends those. That's why you end up with just one column, each iteration you're created a new row.
Solution:
So how do you fix that? Well, you've probably realized by now you need to create a new row and then iterate through all the cells you want to add to that row BEFORE you append that row to your table.
Modifying your code, this might look like so:
function makeCell(tr) {
// Create new td.
const td = document.createElement('td');
// Append td to tr.
tr.appendChild(td);
// return tr object with appended td.
return tr;
}
function makeLine(newTable) {
// Create new tr.
var tr = document.createElement('tr');
// Loop through number of cells.
for (let i = 0; i < 2; i++) {
// Overwrite tr object with each new cell added.
tr = makeCell(tr);
}
// Append new tr to table.
newTable.appendChild(tr);
// Return the table with the new tr.
return newTable;
}
function makeGrid() {
// Select canvas element.
const canvas = document.getElementById('pixelCanvas');
// Create new table.
var newTable = document.createElement('table');
// Loop through the rows, note use var so that I can pass the
// newTable object in to our function and overwrite it each loop.
for (let i = 1; i <= 4; i++) {
// Overwrite with each appended tr.
newTable = makeLine(newTable)
}
// Append completed object to canvas.
canvas.appendChild(newTable);
}
makeGrid();
Note the use of var instead of let, so the object can be overwritten with the new object on each iteration.
Hope that helps!

Related

How to find and select table from Document in Apps Script?

I'm creating a function in Google Apps Script. The purpose of this function is selecting the table from the document and move values to the created Spreadsheet. The problem is that I can't get the table from the document (debugging is OK, but logs show selected table as empty {}).
function addAnswersTable() {
var File = function(Path) { // File object constructor
this.Path = Path;
this.Doc = DocumentApp.openById(this.Path);
this.getTable = new function()
// This function defines
// a getTable method to get
// the first table in the Document
{
if (this.Doc != undefined) {
var range = this.Doc.getBody();
var tables = range.getTables();
var table = tables[0];
return table;
}
}
}
// Creating Excel Table, where first column
// of selected table should be moved
var Table = SpreadsheetApp.create("AnswersTable");
// Creating new File object
var TrueAnswersFile = new File
('1_ne9iBaK-Z36yUYrISr3gru3zw3Qdsneiu14sWnjn34');
// Calling getTable method to get the table placed in File
var TrueAnswersTable = TrueAnswersFile.getTable;
for (var i = 1; i <= TrueAnswersTable.getNumRows; i++) {
// Filling spreadsheet "A" column with cells'
// values ​​from table stored in File
Table.getActiveSheet().getRange("A" + i).setValue(TrueAnswersTable.getCell(1, i).getValue());
};
}
I except the output in Spreadsheet column "A" like :
A1. Just
A2. Cells'
A3. List item with
A4. Values From Table
Actually spreadsheet is empty
You want to retrieve the values from the column "A" of Google Document and put the values to the column "A" of the created Spreadsheet.
The table of index 0 in the Document has 4 rows and 1 column.
The values of each row is Just, Cells', List item with, Values From Table.
I could understand like above. If my understanding is correct, how about this modification?
Modification points:
In your script, the method is not used as the function. By this, the method is not run.
For example, TrueAnswersFile.getTable and TrueAnswersTable.getNumRows.
No method is used.
For example, getValue() of TrueAnswersTable.getCell(1, i).getValue().
new of this.getTable = new function() is not required.
In your script, getCell(1, i) of TrueAnswersTable.getCell(1, i) retrieves the values at from column "B" of the row 2.
If you want to retrieve the values from the row 1 of the column "A", please modify to getCell(i - 1, 0). But in this modification, the start of index is 0. So you can use getCell(i, 0).
When setValue() is used in the for loop, the process cost becomes high. In your case, you can use setValues() instead of it.
When above points are reflected to your script, it becomes as follows.
Modified script:
function addAnswersTable() {
var File = function(Path) {
this.Path = Path;
this.Doc = DocumentApp.openById(this.Path);
this.getTable = function() { // Modified
if (this.Doc != undefined) {
var range = this.Doc.getBody();
var tables = range.getTables();
var table = tables[0];
return table;
}
}
}
var Table = SpreadsheetApp.create("AnswersTable");
var TrueAnswersFile = new File('1_ne9iBaK-Z36yUYrISr3gru3zw3Qdsneiu14sWnjn34');
var TrueAnswersTable = TrueAnswersFile.getTable();
var values = []; // Added
for (var i = 0; i < TrueAnswersTable.getNumRows(); i++) { // Modified
values.push([TrueAnswersTable.getCell(i, 0).getText()]) // Modified
};
Table.getRange("A1:A" + values.length).setValues(values); // Added
}
References:
getCell(rowIndex, cellIndex)
getText()
Benchmark: Reading and Writing Spreadsheet using Google Apps Script

Interactive canvas vs render table in html. Which way is faster?

Which way will be faster and use less memory?
For now i have rendered dynamic <table> by jQuery. Sometimes it has around few thousand cells and it is working so slowly when i do events on it. Html2Canvas is taking alot time to render this table as image. So i wonder about use interactive canvas.
Here is fiddle with script for generating table http://fiddle.jshell.net/j6G66/
I created two examples,
One that mirrors the way you're creating your table
and that's by creating and appending jQuery object elements on every loop iteration:
function createDynamicTable(rows, cols) {
var table = $('<table />');
for(var i=0; i<rows; i++){ // noprotect
var row = $('<tr />');
for(var j=0; j<cols; j++){
var cell = $('<td />');
cell.text("R"+i+"-C"+j);
cell.appendTo( row ); // Appends here....
}
row.appendTo( table ); // Appends here....
}
$('#tableContainer').append( table ); // Appends here....
}
The second one uses a different way of creating a table, that is based instead on the principle of
concatenating a HTML String representation of the needed elements:
function createDynamicTable(rows, cols) {
var table = "<table>";
for(var i=0; i<rows; i++){
var row = "<tr>";
for(var j=0; j<cols; j++){
var cell = "<td>R"+i+"-C"+j+"</td>";
row += cell;
}
row += "</tr>";
table += row;
}
table += "</table>"
$('#tableContainer').append( table ); // Append only once!
}
Now let's be humans and exaggerate a bit creating a table with 1000 rows and 10 cells in each running:
var start = new Date().getTime();
createDynamicTable(1000, 10);
var total = new Date().getTime() - start;
And let's see the results:
IN-LOOP jQuery OBJECTS/EL. CREATION vs. IN-LOOP STRING CONCATENATION
~920 ms ~130 ms
jsBin demo 1 jsBin demo 2
A (logical) side-note on the string concatenation:
you'll not be able to keep copies of alive Objects inside data-* attributes like i.e:
cell = "<td data-objectcopy='"+ myObject +"'>...</td>"
cause the object will result in String "[object Object]", in contrast to jQuery's .data():
cell = $("<td />", {html:"..."}).data("objectcopy", myObject );
where any further change to the object like: $(td).eq(0).data().objectcopy.someProperty = "new value"; will keep it's reference to the original myObject object alive.

Javascript deleteRow causing indexSizeError, need closure

I have this code:
var table = document.getElementById("editTable");
var row = table.insertRow(-1);
var i = row.rowIndex;
var remove = document.createElement("input");
remove.type = "button";
remove.value = "Remove";
remove.onclick = (function() {
var I = i;
return function() {
table.deleteRow(I);
}
})();
var td1 = row.insertCell(-1);
td1.appendChild(remove);
I have read several articles here and I don't understand what I am doing wrong. When I try to delete the last row that I create , I get this error:
IndexSizeError: Index or size is negative or greater than the allowed amount
table.deleteRow(I);
I am pretty sure this is a closure issue. I understand scope but not syntax for anonymous functions in javascript;
I think you're over thinking the whole function/anonymous function/closure stuff here. It's looking a little too complicated. Try this code:
var table = document.getElementById("editTable");
var row = table.insertRow(-1);
var remove = document.createElement("input");
//Append input first so you can get it's parent
var td1 = row.insertCell(-1)
.appendChild(remove);
remove.type = "button";
remove.value = "Remove";
remove.onclick = function () {
var parent = this.parentNode.parentNode; //get the row node
table.deleteRow(parent.rowIndex - 1); //Delete the row index behind it.
};
jsFiddle
Corey, I see you have a working solution but you may be interested in something closer to your original idea.
The problem with your original code appears to be that i becomes an unreliable measure of current row index after other rows have been removed. Trapping i in a closure is not a solution - you just trap a value that is guaranteed to be correct only at the time it is trapped.
However trapping row itself, then obtaining row.rowIndex when it's needed will be reliable, because row.rowIndex gives the current index, not the index at the time the row was appended to the table.
remove.onclick = (function(row) {
return function() {
table.deleteRow(row.rowIndex);
};
})(row);
here is the code working:
var remove = document.createElement("input");
remove.type = "button";
remove.value = "Remove";
remove.onclick = function () {
var parent = this.parentNode.parentNode; //get the row node
table.deleteRow(parent.rowIndex); //Delete the row index behind it.
};
var td1 = row.insertCell(-1)
.appendChild(remove);

Javascript - get all table -> tr values

<table>
<tr><td>foo</td></tr>
<tr><td>bar</td></tr>
<tr><td>abc#yahoo.com</td></tr>
</table>
Can anybody tell me how to write a Javascript line to only grab the email address in the table below, I've been searching a lot, but all I come across is tutorials which use "id" in either table on in td .. I want to do it without having an id .. please help
var rows = document.getElementsByTagName("table")[0].rows;
var last = rows[rows.length - 1];
var cell = last.cells[0];
var value = cell.innerHTML
Try it yourself here: http://jsfiddle.net/ReyNx/.
Obviously you'll have to change document.getElementsByTagName("table")[0] to appropriately match your table
If you're using jQuery it's easier:
var value = $('table tr:last td').text();
For more info, see the MDN DOM reference, which shows you which properties are available on which elements to traverse the DOM.
No jQuery, innerHtml or other evil / heavy functions, just plain old JavaScript:
// Get the first table in the document.
var table = document.getElementsByTagName('table')[0];
// Get the third row of this table (0-index 3rd = 2)
var emailRow = table.rows[2];
// Get this element's content.
var emailContent = emailRow.firstChild.textContent;
You could write it in 1 line:
var emailContent = document.getElementsByTagName('table')[0].rows[2].firstChild.textContent;
If you want to find all email addresses in a table:
var emails = [];
var table = document.getElementsByTagName('table')[0];
var rows = table.rows;
for (var i = 0; i < rows.length; i++) {
var rowText = rows[i].firstChild.textContent;
if (~rowText.indexOf('#')) { // If the content of the row contains a '#' character (This could be replaced with a regex check)
// Also, I personally prefer to use '~' over '> -1' for indexOf(), but both would work.
emails.push(rowText);
}
}
console.log(emails);
Working example
If like me you want to get the text from all the first column items in all the tables on the page then use this.
jQuery('table tr td').each( function( cmp ) {
console.log( jQuery(this).text() );
} );
I wanted to extract all emails, but I had more than 1,000 rows and 17 columns/cells.
I used vanilla js, made some adjustments to get my desired output
var table = document.getElementsByTagName("table")[0]; //first table
var rows = table.rows;
//loop through rows
for (var i = 0; i < rows.length; i+=1) {
var emailTr = rows[i];
var emailTd = emailTr.cells[2]; //target third column/cell
var email = emailTd.innerHTML; //get the value
console.log(email + ', ');
var node = document.createElement("span"); // create span element
node.innerHTML = email + ', '; // desired output
document.body.appendChild(node); // display to document body
}
Assuming you're using vanilla Javascript (no libraries such as jQuery), and that this is the only table on the page, you can use the following code to select the third tr in the table, then find out what the td element contains
var table = document.getElementsByTagName("table")[0];
var emailTr = table.rows[2];
var emailTd = emailTr.cells[0];
var email = emailTd.innerHTML;
jQuery would make this easier
var email = $("table").children("tr:eq(2)").children("td").html();
A simple way is to give it a common class. Try:
<table>
<tr><td class="email">foo</td></tr>
<tr><td class="email">bar</td></tr>
<tr><td class="email">abc#yahoo.com</td></tr>
</table>
<script>
function getEmail(){
var email = new Array();
var arr = document.getElementsByClassName('email');
for(var i=0; i< arr.length; i++){
email.push(arr[i].innerHTML);
}
alert(email.join(','));
}
</script>
Demo
This is a solution in case you are using or plan to use jQuery library.
Given the email is always in the third row and first column (like in your example) then you can do as follows:
email = $('table tr:nth-child(3) td:first-child').html();
See working demo
Get all the <tr> elements. Loop through each one and compare the innerHTML against a regex that matches email addresses.
var emailAddresses = [];
var cells = document.getElementsByTagName("td");
for (var i = 0; i < cells.length; i++) {
if (cells[i].innerHTML.match(/yourEmailRegex/g)) {
emailAddresses[emailAddresses.length] = cells[i].innerHTML;
}
}
Find the appropriate regular expression here http://www.regular-expressions.info/email.html
in my case i want fifth column value of last row
var rows = document.getElementsByTagName("tbody")[0].rows;
var last = rows[rows.length - 1];
var cell = last.cells[4];
console.log(cell.textContent);

Walking a table

I am using jQuery to inject values from an JSON object into an existing, empty table.
What I want to do is fill up my table top-to-bottom, instead of what it does now - left to right. So how would I modify this snippet to accomplish what I want?
_.each(dataset, function(v, k){
$("tr td:empty:first", "#myTable tbody").html("<strong>"+v+"</strong>");
});
I guess that It would be possible to target the td in the column which has the fewest filled rows, but how?
Change your selector to:
$("tr td:empty:first:first-child", "#myTable tbody")
So that it matches only when the table cell is the first-child of it's parent, the row, so will only insert in the first column.
Edit: A method to populate the table based on a known and limited number of columns
It works by selecting each table column using the nth-child of each row, then concatenating the columns together, and filtering for the first empty one.
var cells = [].concat(
$('tr td:nth-child(1)').get(),
$('tr td:nth-child(2)').get(),
$('tr td:nth-child(3)').get(),
$('tr td:nth-child(4)').get()
)
_.each(dataset, function(v, k){
$(cells).filter(':empty:first').html("<strong>"+v+"</strong>");
});
Edit: Generic version
// create array to hold cells ordered by column
var cells = [];
// loop through desired columns adding to cells array
// hint: you can specify column `from` and `to` in the for loop definition
for(i=1;i<=4;i++)
cells = cells.concat($('tr td:nth-child('+i+')').get());
// loop through the data, populating each column in order
_.each(dataset, function(v, k){
$(cells).filter(':empty:first').html("<strong>"+v+"</strong>");
});
Is dataset an array? If so, something like this:
var self;
$('#myTable tr:has("td:empty")', function (index, elem) {
if (index < dataset.length) {
self = $(this);
self.find('td:empty').html("<strong>"+dataset[i]+"</strong>")
}
});
..fredrik
Here's what I ended up doing, using underscore.js to iterate the dataset:
var currentCol = 0;
var currentRow = 0;
var renderedCount = 0;
_.each(dataset, function(v, k) {
/* Switch to a new column when we have rendered enough (4)
* elements vertically per column and reset the rowcounter.
*/
if (renderedCount % 4 === 0) {
currentCol++;
currentRow = 0;
}
// Inject our value as HTML into the first empty TD per column
$("tr:eq("+currentRow+") td:empty:first", "#mytable tbody").html(v);
// Increment current row count and our renderedCount
currentRow++;
renderedCount++;
});
Many thanks to #BillyMoon for helping out!
Update:
To create the empty table before doing the above, I did this:
// To allow repeating strings (n) times. Borrowed from someone on SO
String.prototype.repeat = function( num ) {
return new Array( num + 1 ).join( this );
}
// Setup our wanted maximum amount of columns horizontally and our table structure
var maxColsPerRow = 4;
var rowHTML = "<tr>"+'<td></td>'.repeat(maxColsPerRow)+"</tr>";
var datasetCount = _.keys(dataset).length; // Amount of properties in our dataset calculated with underscore.js keys()-method.
// Append as many empty rows&cells to our table that we'll need
$("#myTable tbody").html( rowHTML.repeat( Math.round( datasetCount/maxColsPerRow));

Categories