I'm trying to create a simple table with jQuery.
Currently I got on the browser one single black cube, while the goal is to have table with 5 rows and 5 cols cubes colored black.
Html:
<table class="t"> </table>
CSS:
.box {
width:50px;
height:50px;
background-color:black
}
JS
let $table = $('.t')
let $td = $('<td>').addClass('box')
for (let i = 0; i < 5; i++)
{
let $tr = $('<tr>')
for (let k = 0; k < 5; k++) {
$tr.append($td)
$table.append($tr)
}
$tr.appendTo($table);
}
I have some javascript that generates a variable number of tables. Currently, these tables are displayed in a list down the page:
However, this means that, if there are more than about four tables, the bottom ones are not visible and I need to scroll, meaning I can't see the top ones.
So, what I would like to do is to somehow 'flow' the tables (I can make them narrower) across the page and then on to a new line. So, if I have five tables, then I have (say) two columns on the page, with heats 1, 3 and 5 appearing in column 1; and heats 2 and 4 in column 2.
Here is the section of the code that deals with this:
numGroups = groups.length;
for (var g = 0; g < numGroups; g++)
{
// Create a new table for each group
var t=document.createElement('table');
t.style.borderCollapse = 'collapse';
t.style.cellPadding = '5px';
// Create table header showing group number
var caption = document.createElement( "caption" );
caption.style.textAlign = 'left';
caption.style.paddingTop = '10px';
caption.style.color = "white";
thisGroup = (g+1);
caption.appendChild(document.createTextNode("Group "+thisGroup));
t.appendChild(caption);
var headers = ["Pos", "Driver", "Score", "Best Lap"];
for (var i = 0; i < headers.length; i++)
{
var th = document.createElement( "th" );
th.style.color = headerColour;
th.style.border= theBorderWidth + borderColour;
th.appendChild(document.createTextNode(headers[i]));
t.appendChild(th);
}
// Create a table record for each driver in the group
numGroupDrivers = groups[g].length
for (var k = 0; k <numGroupDrivers; k++) //run through each of the drivers in the heat.
{
var tr=document.createElement('tr'); //create variable 'tr' to create a table row
tr.style.backgroundColor = "transparent";
tr.style.color = textColour;
var name = groups[g][k]; //variable name = nickname
if (name == null && config.d) { //if name isn't blank and this is a digital race...
continue;
}
// Create column for position
var tdPos=document.createElement('td'); //create variable 'tdPos' to create a table cell with data
tdPos.style.width='50px';
tdPos.style.textAlign='center';
tdPos.style.border=theBorderWidth + borderColour;
tdPos.appendChild(document.createTextNode(k+1)); //go through the table in order setting tdPos to row number
tr.appendChild(tdPos); //add tdPos to table record
var tdName=document.createElement('td'); //create variable 'tdName' to create a table cell with data
tdName.style.width='250px';
tdName.style.textAlign='center';
tdName.style.border=theBorderWidth + borderColour;
tdName.appendChild(document.createTextNode(name));
tr.appendChild(tdName);
//Create column for score
var tdScore=document.createElement('td');
tdScore.style.width='80px';
tdScore.style.textAlign='center';
tdScore.style.border=theBorderWidth + borderColour;
for (var l = 0; l <scoreArray.length; l++)
{
if (groups[g][k] == scoreArray[l][0])
{
if (scoreArray[l] == 0)
{
tdScore.appendChild(document.createTextNode("--"));
} else
{
tdScore.appendChild(document.createTextNode(scoreArray[l][1]));
}
}
tr.appendChild(tdScore);
t.appendChild(tr);
}
//Create column for best lap
var tdTime=document.createElement('td');
tdTime.style.width='120px';
tdTime.style.textAlign='center';
tdTime.style.border=theBorderWidth + borderColour;
for (var l = 0; l <scoreArray.length; l++)
{
if (groups[g][k] == scoreArray[l][0])
{
if (scoreArray[l][2] == -1)
{
tdTime.appendChild(document.createTextNode("--"));
} else
{
tdTime.appendChild(document.createTextNode(scoreArray[l][2]));
}
}
tr.appendChild(tdTime);
t.appendChild(tr);
}
}
groupTables[g] = t;
}
Any help gratefully received!
Thanks,
Connal
This isn't a direct answer to your question.
In spirit, though, I think it's the best answer you'll get...
Learn css-flex. JavaScript as presentational layer will be brittle and is not the optimal place for it anyway. On a large screen and mouse (i.e. a laptop or desktop but not a phone) take a look at MDN's tutorial on flex. You'll be able to get what you want in a way that
degrades nicely,
is faster,
is less reliant on platform/browser,
already debugged,
helps you learn another browser-native technology that you'll have on your tool belt tomorrow
might possibly be more accessible to screen readers and other aids for the visually impaired,
flows better, and smoothly, when someone resizes their screen or changes the font size.
Bonus: Anyone in the future maintaining your code (including and especially youself) will find it much easier.
I had resisted learning flex for years, choosing instead to keep moving with my then-current projects as fast as I could. I regret that. I'm screwed; I'll never get that time back. My best way to pay it forward is to highly recommend you give it a shot.
If anyone has another great link for intro to CSS flex that they recommend, please comment.
So, if you adopt this approach, then instead of a TABLE tag contains TR tags containing TD tags, you'll need to generate a DIV (or SECTION) tag that has a specific class attribute, containing a DIV (or ARTICLE) tag per "row", which contain DIV tags per "cell", and after that it's all CSS.
If you're still not convinced, try looking at CSS Zen Garden for examples of how, if you organize your HTML to tell the browser only "what the information is" and leave "what it should look like" to CSS, both tasks are easier to accomplish.
As per my comment, You might set width: 45%; display: inline-table to your tables:
var groups = [
['John', 'Sam', 'Peter'],
['John', 'Sam', 'Peter'],
['John', 'Sam', 'Peter'],
['John', 'Sam', 'Peter'],
['John', 'Sam', 'Peter'],
],
scoreArray = [
[],
[],
[],
[],
[]
],
g = 0,
headerColour = 'gold',
textColour = 'black',
borderColour = 'black',
theBorderWidth = 'solid 1px ';
groups.forEach(idx => {
// Create a new table for each group
var t = document.createElement('table');
t.style.width = '45%';
t.style.display = 'inline-table';
t.style.marginRight = '2%';
t.style.borderCollapse = 'collapse';
t.style.cellPadding = '5px';
// Create table header showing group number
var caption = document.createElement("caption");
caption.style.textAlign = 'left';
caption.style.paddingTop = '10px';
caption.style.color = "white";
thisGroup = (g + 1);
caption.appendChild(document.createTextNode("Group " + thisGroup));
t.appendChild(caption);
var headers = ["Pos", "Driver", "Score", "Best Lap"];
for (var i = 0; i < headers.length; i++) {
var th = document.createElement("th");
th.style.color = headerColour;
th.style.border = theBorderWidth + borderColour;
th.appendChild(document.createTextNode(headers[i]));
t.appendChild(th);
}
// Create a table record for each driver in the group
numGroupDrivers = groups[g].length
for (var k = 0; k < numGroupDrivers; k++) //run through each of the drivers in the heat.
{
var tr = document.createElement('tr'); //create variable 'tr' to create a table row
tr.style.backgroundColor = "transparent";
tr.style.color = textColour;
var name = groups[g][k]; //variable name = nickname
if (name == null && config.d) { //if name isn't blank and this is a digital race...
continue;
}
// Create column for position
var tdPos = document.createElement('td'); //create variable 'tdPos' to create a table cell with data
tdPos.style.width = '50px';
tdPos.style.textAlign = 'center';
tdPos.style.border = theBorderWidth + borderColour;
tdPos.appendChild(document.createTextNode(k + 1)); //go through the table in order setting tdPos to row number
tr.appendChild(tdPos); //add tdPos to table record
var tdName = document.createElement('td'); //create variable 'tdName' to create a table cell with data
tdName.style.width = '250px';
tdName.style.textAlign = 'center';
tdName.style.border = theBorderWidth + borderColour;
tdName.appendChild(document.createTextNode(name));
tr.appendChild(tdName);
//Create column for score
var tdScore = document.createElement('td');
tdScore.style.width = '80px';
tdScore.style.textAlign = 'center';
tdScore.style.border = theBorderWidth + borderColour;
for (var l = 0; l < scoreArray.length; l++) {
if (groups[g][k] == scoreArray[l][0]) {
if (scoreArray[l] == 0) {
tdScore.appendChild(document.createTextNode("--"));
} else {
tdScore.appendChild(document.createTextNode(scoreArray[l][1]));
}
}
tr.appendChild(tdScore);
t.appendChild(tr);
}
//Create column for best lap
var tdTime = document.createElement('td');
tdTime.style.width = '120px';
tdTime.style.textAlign = 'center';
tdTime.style.border = theBorderWidth + borderColour;
for (var l = 0; l < scoreArray.length; l++) {
if (groups[g][k] == scoreArray[l][0]) {
if (scoreArray[l][2] == -1) {
tdTime.appendChild(document.createTextNode("--"));
} else {
tdTime.appendChild(document.createTextNode(scoreArray[l][2]));
}
}
tr.appendChild(tdTime);
t.appendChild(tr);
}
}
document.body.appendChild(t);
})
Use CSS with an external stylesheet and/or a <style> tag at the bottom of the <head>. You can unclutter the JavaScript by removing all of the expressions with the .style property. Use .class to apply CSS styles to the tags. In the example below, are 7 tables. When there are 5 or less tables, they have width: 100%. When there are more than 5 tables all tables are given the .half class which decreases their widths to 50%. The following styles will automatically arrange the tables in 2 columns when they have class .half:
main { display: flex; flex-flow: row wrap; justify-content: flex-start;
align-items: center;...}
/* Flexbox properties will arrange the tables in two columns when
there are more than 5 of them (because .half will be added to
each table */
.half { width: 50%; }
This flow control statement is responsible for the class change:
if (qty > 5) {
tables.forEach(t => t.classList.add('half'));
} else {
tables.forEach(t => t.classList.remove('half'));
}
Also, it's important that you have full control of the tables, in the example, it fetch()es data from a test server to create as many tables as the qty parameter dictates (in example, it's heats = 7). Normally table column widths are determined by content which makes them sporadically unseemly (especially with dynamic content). table-layout: fixed allows you to set the widths of the columns by adding explicit widths directly to the <th> (or the top <td> if <th> are not present):
table { table-layout: fixed; ...}
BTW, the Total Time does not coincide with Position (ie. lowest Total Time should be matched with Position: 1). If you want to sort the columns you'll need to start another question.
<!DOCTYPE html>
<html lang="en">
<head>
<title>NASCAR HEAT</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<style>
*, *::before, *::after { box-sizing: border-box; }
:root { font: 1ch/1 'Segoe UI'; }
html, body { width: 100%; min-height: 100%; margin: 0; padding: 0; }
body { font-size: 2ch; color: white; background: linear-gradient(to bottom, #85a4e5 13%,#053cbd 66%); }
main { display: flex; flex-flow: row wrap; justify-content: flex-start; align-items: center; width: 100%; height: 100%; margin: 0 auto; }
table { table-layout: fixed; width: 100%; border-collapse: collapse; border: 1px solid white; }
caption { font-size: 1.35rem; font-weight: 900; text-align: left; }
th, td { border: 1px solid white; text-align: center; }
th:nth-of-type(2), td:nth-of-type(2) { text-align: left; }
th { font-size: 1.25rem; overflow: hidden; }
td { font-size: 1.15rem; }
th:first-of-type { width: 5%; }
th:nth-of-type(2) { width: 55%; }
th:nth-of-type(3) { width: 15%; }
th:nth-of-type(4) { width: 15%; }
th:last-of-type { width: 15%; }
.half { width: 50%; }
</style>
</head>
<body>
<main></main>
<script>
let heats = 7;
function buildTables(selector, qty = 1) {
const headers = ['Position', 'Driver', 'Average Lap', 'Best Lap', 'Total Time'];
const base = document.querySelector(selector) || document.body;
for (let i = 0; i < qty; i++) {
let t = document.createElement('table');
let tB = document.createElement('tbody');
let cap = t.createCaption();
cap.textContent = `Heat ${i + 1}`;
t.append(tB);
let tH = t.createTHead();
let hRow = tH.insertRow();
for (let j = 0; j < 5; j++) {
hRow.insertCell().outerHTML = `<th>${headers[j]}</th>`;
}
base.append(t);
}
const tables = [...document.querySelectorAll('table')];
for (let k = 0; k < qty; k++) {
fetch('https://my.api.mockaroo.com/nascar.json?key=3634fcf0').then((res) => res.json()).then(res => {
let row;
for (let r in res) {
row = `<tr>
<td>${res[r].Position}</td>
<td>${res[r].Driver}</td>
<td>${res[r]['Average Lap']}</td>
<td>${res[r]['Best Lap']}</td>
<td>${res[r]['Total Time']}</td>
</tr>`;
tables[k].tBodies[0].insertAdjacentHTML('beforeEnd', row);
}
});
}
if (qty > 5) {
tables.forEach(t => t.classList.add('half'));
} else {
tables.forEach(t => t.classList.remove('half'));
}
};
buildTables('main', heats);
</script>
</body>
</html>
I have a function that dynamically creates DOM nodes to accomodate GPS JSON data from a server. As number of nodes increases, browser performance also
degrades. Is there any means to rewrite/tweak the function to improve performance? The simplified node creation part of the function works as shown below:
var table = document.createElement('table');
var tbody = document.createElement('tbody');
table.appendChild(tbody);
for(var i = 0; i<3000;i++){
var tr = document.createElement('tr');
for(var j=0;j<50;j++){
var td = document.createElement('td');
td.style.backgroundColor="#a"+j%10+'c';
tr.appendChild(td);
}
tbody.appendChild(tr);
}
You might want to consider using some kind of Recyclable List.
Such lists recycle their elements by deleting DOM nodes that lie outside the viewport and recreating them when currently viewed.
Long story short - only currently-viewed DOM nodes exist in the DOM at any given time
Another technique is to use a fixed number of cells which you then fill with data according to where the user is scrolled
AFAIK there's no other way to go about this - naively creating 150,000 DOM nodes would completely decimate your scrolling performance
Some Examples
An example in pure JS
An example that's build on top of Polymer
Another example build on top of React
Here's how the pure JS example handles this:
Source: http://elliottsprehn.com/personal/infinite-scroll.html
<!DOCTYPE html>
<style>
* { box-sizing: border-box; }
ul, li { list-style-type: none; padding: 0; margin: 0; }
h1 { font-size: 1.2em; }
#infinite-list {
width: 300px;
border: 1px solid #ccc;
margin: 1em;
position: relative;
}
#infinite-list .scroll-view {
overflow-y: scroll;
height: -webkit-calc(20em + 2px);
}
#infinite-list ul {
position: absolute;
top: 0;
width: -webkit-calc(100% - 16px);
}
#infinite-list li {
height: 2em;
line-height: 2em;
padding-left: 1em;
border-bottom: 1px solid #ccc;
}
#infinite-list li:last-child {
border-bottom: none;
}
#infinite-list .scroll-view .spacer {
height: -webkit-calc(2em * 20000);
}
</style>
<h1>Infinite Scrolling with a fixed number of DOM nodes</h1>
<div id="infinite-list">
<div class="scroll-view">
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<div class="spacer"></div>
</div>
</div>
<script>
(function() {
function randRange(low, high) {
return Math.floor(Math.random() * (high - low)) + low;
}
function randPick(array) {
return array[randRange(0, array.length)];
}
// Randomly create a bunch of test data.
var tlds = ['.com', '.net', '.org', '.edu', '.co.uk'];
var domains = ['google', 'facebook', 'yahoo', 'apple', 'youtube', 'amazon'];
var data = [];
for (var i = 0; i < 20000; ++i) {
data.push(i + ' ' + randPick(domains) + randPick(tlds));
}
var list = document.querySelector('#infinite-list');
var listItems = document.querySelectorAll('#infinite-list li');
var scrollView = document.querySelector('#infinite-list .scroll-view');
var itemHeight = listItems[0].getBoundingClientRect().height;
var previous;
// Propagate scrolling with the mouse wheel.
list.onmousewheel = function(e) {
var delta = e.wheelDeltaY;
if (Math.abs(delta) < itemHeight) {
delta = itemHeight * (delta > 0 ? 1 : -1);
}
scrollView.scrollTop -= delta;
};
function update() {
var current = scrollView.scrollTop;
if (previous == current) {
webkitRequestAnimationFrame(update);
return;
}
previous = current;
var first = Math.ceil(current / itemHeight);
for (var i = 0; i < listItems.length; ++i) {
listItems[i].firstElementChild.textContent = data[first++];
}
webkitRequestAnimationFrame(update);
}
update();
})();
</script>
If you are not already, you might want to hide the element you are creating nodes in, and only show it at the end of your loop.
E.g. set display:none; on it while you are working on it, and then switch back to display:block; (or whichever it is) afterwards.
Update: Actually, you don't even need to hide it, though that's one way to do it. The cleaner way is to wait with adding your table to the document until you finish adding all nodes to it, and then only add it to the body afterwards.
var table = document.createElement('table');
var tbody = document.createElement('tbody');
for(var i = 0; i<3000;i++){
var tr = document.createElement('tr');
for(var j=0;j<50;j++){
var td = document.createElement('td');
td.innerHTML = "#a"+j%10+'c';
td.style.backgroundColor="#a"+j%10+'c';
tr.appendChild(td);
}
tbody.appendChild(tr);
}
table.appendChild(tbody);
document.body.appendChild(table);
It might help to enlist the browser's help by making a template for a single row, then cloning it repeatedly:
var table = document.createElement('table');
var tbody = document.createElement('tbody');
table.appendChild(tbody);
// Make a template row once up front
var rowtemplate = document.createElement('tr');
for(var j=0;j<50;j++){
var td = document.createElement('td');
td.style.backgroundColor="#a"+j%10+'c';
tr.appendChild(td);
}
// Now clone it over and over instead of creating it piecemeal repeatedly
// Must pass true to ensure it clones whole tree, not just top level tr
for(var i = 0; i<3000;i++){
table.appendChild(rowtemplate.cloneNode(true));
}
Obviously, it depends on the browser internals whether this helps, but typically, the browser should be able to clone an existing node tree faster than manually creating each element from scratch.
I have an empty div.
<div id="container"></div>
With the following CSS:
#container{
display: flex;
position: relative;
flex-flow: row wrap;
width: 100%;
}
In which I want to insert more divs via JavaScript but let the user choose how many rows and columns the container will have.
At the moment this is the code I have:
var container = document.getElementById('container');
var rows = prompt("How much lines do you want?");
var columns = prompt("And how much columns do you want?");
for(var i = 0; i < rows; i++){
for(var j = 0; j < columns; j++){
var div = document.createElement("div");
div.classList.add("square");
div.innerHTML="<p>" + i + "</p>";
container.appendChild(div);
}
}
where square class has the following:
.square{
flex: 1;
height: 50px;
}
but all the divs inside the container are displayed in one row.
What I want to do is to set the divs inside the container with the same dimensions that the user inputs, but I cannot get it.
Is it possible to set these divs inside the container as rows/columns given by the user?
Note: As you can see, I am only using JavaScript and I would like to keep it, so please avoid answers with plugins/libraries/etc.
If you have N columns, one solution is to set the flex-basis property to a percentage of 100/N.
You can set it in javascript without libraries (as required) with:
div.style['flex-basis'] = 100/columns + '%';
Keeping the same settings as yours, you just have to add the previous line in the script like this:
var container = document.getElementById('container');
var rows = prompt("How much lines do you want?");
var columns = prompt("And how much columns do you want?");
for(var i = 0; i < rows; i++){
for(var j = 0; j < columns; j++){
var div = document.createElement("div");
div.classList.add("square");
div.innerHTML="<p>" + i + "</p>";
div.style['flex-basis'] = 100/columns + '%';
container.appendChild(div);
}
}
You can test it with the codepen : http://codepen.io/anon/pen/wGYKdY
I am trying to create a table width javascript and createElement().
My problem is that it does not set any background image (just plain white).
However, if I set the innerHTML of the to an image tag with same path, it works!
create : function () {
var tbody = document.createElement('tbody');
for(i in this.map) {
var tr = document.createElement('tr');
for(j in this.map[i]) {
var td = document.createElement('td');
var cell = this.map[i][j];
td.style.color = "red";
console.log("gfx/tile_"+this.backgrounds[cell]+".png");
td.style.backgroundImage = "url(gfx/tile_"+this.backgrounds[cell]+".png);";
td.innerHTML = "j";
tr.appendChild(td);
}
tbody.appendChild(tr);
}
this.gameDOMelm.appendChild(tbody);
}
I also have another problem that there's space between each ROW in the table.
Here's the DOM Element I'm appending to:
<table id="gameField" cellpadding="0" cellspacing="0"></table>
And the CSS
* {padding: 0; margin: 0;}
td {
min-width: 32px;
min-height: 32px;
width: 32px;
height: 32px;
}
These problems occur both in Chrome and FF # ubuntu 11.04.
No errors shows in javascript console.
jack
Try "url('gfx/tile_"+this.backgrounds[cell]+".png')" (with single quotes around the URL).