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'm making a simple tic tac toe game as my introduction to JS and I ran into a problem almost immediately. I have a div with the class="container", and I use JS to create 9 more div elements inside it.
I have created the div elements with and put them in the cells[] array with no problem. The problem arrises when i try to access .style from the array elements.
const container = document.getElementById("container");
const cells = [];
for (let i = 0; i < 9;) {
cells[i] = document.createElement("div");
container.appendChild(cells[i]);
cells[i].onclick = function(){cells[i].style.backgroundColor = "red";} //this line is where the problem is
i++;
}
I have gone about this using addEventHandler() too, still with me not being able to access the .style property. When I type it in it doesn't show up on that autofill thing VSCode does.
Help?
Ps. I have noticed the cells[] array can't always access it's elements when inside a block.
This issue is due to how closures work in JavaScript.
Here's a demo with your current code (plus some CSS to make it clear what's happening):
const container = document.getElementById("container");
const cells = [];
for (let i = 0; i < 9;) { // actually, the problem is here...
cells[i] = document.createElement("div");
container.appendChild(cells[i]);
cells[i].onclick = function(){cells[i].style.backgroundColor = "red";} // ...here...
i++; // ...and here
}
div:not([class]) {
height: 20px;
border: 1px solid white;
background: cornflowerblue;
}
div:hover {
opacity: .5;
}
<container id="container"></container>
Note that the next div is always highlighted, not the one that was clicked.
Because you increment i within the block itself, that value is captured by the onclick callback, so it's always 1 higher than it should be.
Instead, you need to increment i within the parentheses as the third setup statement for the loop itself.
Here's the fix:
const container = document.getElementById("container");
const cells = [];
for (let i = 0; i < 9; ++i) { // increment here...
cells[i] = document.createElement("div");
container.appendChild(cells[i]);
cells[i].onclick = function() { cells[i].style.backgroundColor = "red"; }
// ...not here
}
div {
height: 20px;
border: 1px solid white;
background: cornflowerblue;
}
div:hover {
opacity: .5;
}
<container id="container"></container>
const container = document.querySelector('.container');
for (let i = 0; i < 9; ) {
const div = document.createElement('div');
container.appendChild(div);
div.addEventListener('click', chanegColor);
div.classList.add('setWidth');
i++;
}
function chanegColor() {
this.style.backgroundColor = 'red';
}
.setWidth {
width: 100px;
height: 100px;
border: 1px solid black;
}
<div class="container"></div>
const container = document.querySelector('.container');
for (let i = 0; i < 9; ) {
const div = document.createElement('div');
container.appendChild(div);
div.addEventListener('click', chanegColor);
div.classList.add('setWidth');
i++;
}
function chanegColor() {
this.style.backgroundColor = 'red';
}
I'm very new to HTML and javascript but I need to create a grid container of buttons in which the text displayed on the button is an element in a list. in other words, I need each button's text to be different from one another. I was thinking to use a for loop but I'm not sure that's the way to go. I started with making the text of all buttons similar but it still isn't working. any help or advice at this point will be greatly appreciated!!
.categories-buttons {
display: grid;
grid-template-columns: 80px 80px 80px 80px 80px;
}
.item {
padding: 10px;
}
<div class="categories-buttons">
<button id="demo" class="item"></button>
<script>
for (var i = 0; i < 20; i++) {
document.getElementById("demo")(7);
}
</script>
</div>
Lets assume your list is an array of elements:
// This is javascript array of objects, each object has 1 property - buttonText.
// You can add more properties.
const elements = [
{ buttonText: 'Button 1' },
{ buttonText: 'Button 2' },
{ buttonText: 'Button 3' }
];
// Get parent div in which you want to add buttons
const parent = document.getElementById('buttons-container');
// In for loop, set "i" to be lower than number length of array.
for(let i = 0; i < elements.length; i++) {
// Create button node and add innerHTML (innerHTML is stuff that goes between <></> tags).
// Since "elements" is an array, you select current iteration of it with [i]
let button = document.createElement('button');
button.innerHTML = elements[i].buttonText;
parent.appendChild(button);
}
<div id="buttons-container"></div>
Something like this?
.categories-buttons {
display: grid;
grid-template-columns: 80px 80px 80px 80px 80px;
}
.item {
padding: 10px;
}
<div class="categories-buttons">
</div>
<script type="text/javascript">
var div = document.getElementsByClassName("categories-buttons")[0];
for (var i = 0; i < 20; i++) {
var btn = document.createElement("button");
btn.innerText = "Button " + i;
div.append(btn);
}
</script>
I'm creating a grid 16x16 with javascript. I also want the grid to do something when the mouse hover over each square. The problem is that I do not know how to add a class in a iterative way that every div get the same class.
Doing so, I would make jQuery do the action with one function applied to that class.
This is my present code
var contador = 1;
var outra = document.createElement('div');
outra.id = 'container';
document.body.appendChild(outra);
for (i=1;i<=16;i++){
for (j=1;j<=16;j++){
var divCreation = document.createElement('div');
var created = divCreation;
created.id = "numero"+ contador;
console.log(created.id);
created.textContent = ". ";
contador = contador + 1;
outra.appendChild(created);
}
}
$('#numero'+contador).hover(function(){
$(this).css('background-color','yellow');
});
If you want to give a specific class to your dynamically created elements, you can do it using className property.
Your code would be like this:
divCreation.className = "someClass";
Note:
I don't see why you are setting divCreation in a new variable, it's
just useless.
And instead of detecting hover with jQuery, you can just do it with css, look at my Demo below, or if you want to keep jquery you can use the class selector like this $('.box').hover(...).
Demo:
var outra = document.createElement('div');
outra.id = 'container';
var contador = 1;
document.body.appendChild(outra);
for (i = 1; i <= 16; i++) {
for (j = 1; j <= 16; j++) {
var created = document.createElement('div');
created.className = "box";
created.id = "numero" + contador;
created.textContent = ". ";
contador++;
outra.appendChild(created);
}
}
.box:hover {
background-color: yellow;
}
No need for use of ID for this at all...just use a common class. Also since using jQuery can create all this with a lot less code using by using it.
var $outra = $('<div>', { id: 'container'});
for (i = 1; i <= 16; i++) {
for (j = 1; j <= 16; j++) {
$outra.append( $('<div>', { class: 'box', text: '.'}) );
}
}
$('body').append($outra);
$('.box').hover(function() {
$(this).toggleClass('yellow');
});
.box {
width: 50px;
height: 50px;
display: inline-block;
border: 1px solid #ccc
}
.box.yellow {
background-color: yellow
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You can use the attribute starts with selector
$('[id^=numero').hover(
or provide a class to the element and target the elements using the class
This will select all the elements whose id starts with the value you are specifying and attach a hover event.
And when you are using jQuery, why do you want to mix in both Vanilla JS and jQuery. You can just stick with one.
var contador = 1;
var $outra = $('<div/>', {
id: 'container',
class: 'container'
});
$('body').append($outra);
for (i=1;i<=16;i++){
for (j=1;j<=16;j++) {
var $divCreation = $('<div/>', {
id: 'numero' + contador,
text: '. ',
class: 'box'
});
$outra.append($divCreation);
contador = contador + 1;
}
}
$('[id^=numero').hover(function(){
$(this).css('background-color','yellow');
});
.container {
width: 200px;
height: 200px;
border: 1px solid green;
}
.box {
width: 20px;
height: 20px;
border: 1px solid black;
background: green;
float: left;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I have the following dynamically generated arrays:
,<div id="layer0" style="left: 470px; top: 286px;">Some Text</div>
,<div id="layer0" style="font-size: 68px; left: 70px; top: 286px; ">SomeText</div>
,<div id="layer1" style="font-size: 18px; left: 60px; top: 286px; ">SomeText</div>
,<div id="layer2" style="font-size: 18px; left: 50px; top: 286px; ">SomeText</div>
The first 2 entries are not exactly duplicates but have the same id="layer0". The second one is different because it has a CSS font-size propriety.
How can I remove the first any from this array that has a duplicate id but may differ in the exact form?
The arrays are combined together trough:
var allcode = $.merge([oldarray],[newarray])
Where in oldarray are some duplicates I need to get rid of.
Thank you.
I think you'd be ahead to more carefully combine the arrays, rather than mash them together and clean up later.
function matchId(htmlstring){
var match = htmlstring.match( new RegExp(/id=\"([^\"]+)\"/i) );
if (match && match[1]) {
return match[1];
}
return '';
}
for (var j=0; j < oldarray.length; j++) {
var exists = false;
for (var k=0; k < newarray.length; k++) {
var newId = matchId(newarray[k]);
var oldId = matchId(oldarray[j]);
if (newId == oldId) {
// element already exists.
exists=true;
break;
}
}
if (!exists) {
newarray.push( oldarray[j] );
}
}