Hi guys am having trouble with this, When I declare the elements outside the loop the script ends up being broken. I plan to use the elements in several functions. Tried a lot of times but didn't find a solution. Broke the project a few times while doing guess work. Also If there's any idea on how to shorten this or to make it work better please give me a heads up :)
async function loadTable() {
try {
while(dpTable.firstChild) dpTable.removeChild(dpTable.firstChild);
const result = await fetch('http://localhost:5000/users/student_data', { method: 'GET' });
const table = await result.json();
table.forEach((res) => {
//Create Table elements and then load results into the elements
const tr = document.createElement('tr');
const studentNo = document.createElement('td');
const name = document.createElement('td');
const surname = document.createElement('td');
const date_joined = document.createElement('td');
const fees_paid = document.createElement('td');
const fees_owing = document.createElement('td');
const is_owing = document.createElement('td');
const trash_btn = document.createElement('button');
const trash_Icon = document.createElement('i');
tr.id = 'Content';
studentNo.id = res.id;
name.id = 'fname';
surname.id = 'lname';
fees_paid.id = 'amount_paid';
fees_owing.id = 'amount_owing';
is_owing.id = 'debt';
//Enter the data from the response
studentNo.innerText = res.id;
name.innerText = res.fname;
surname.innerText = res.lname;
fees_paid.innerText = res.amount_paid;
fees_owing.innerText = res.amount_owing;
date_joined.innerText = res.date_joined;
is_owing.innerText = res.is_owing;
trash_btn.innerText = 'Delete';
//Add Classes for elements
studentNo.classList.add('trContent');
name.classList.add('trContent');
surname.classList.add('trContent');
fees_paid.classList.add('trContent');
fees_owing.classList.add('trContent');
date_joined.classList.add('trContent');
is_owing.classList.add('trContent');
trash_Icon.classList.add('fas');
trash_Icon.classList.add('fa-trash');
trash_Icon.classList.add('trash-btn');
//Append table row elements to main table
tr.append(studentNo);
tr.append(name);
tr.append(surname);
tr.append(fees_paid);
tr.append(fees_owing);
tr.append(date_joined);
tr.append(is_owing);
tr.append(trash_btn);
//Event Listeners
trash_btn.addEventListener('click', async (e) => {
if (window.confirm('Delete')) {
console.log('trash icon clicked');
const jsonReq = {};
jsonReq.id = res.id;
const result = await fetch('http://localhost:5000/users/student_data', {
method: 'DELETE',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(jsonReq),
});
alert('Deleted Record');
window.location.reload();
}
});
//Append the table as a child to the main element
dpTable.appendChild(tr);
});
} catch (e) {
console.log(`Cant load List ${e}`);
}
}
There are a couple of minor issues you need to remedy or perhaps even ignore
Element IDs must be unique so you can't use the same id for multiple cells. You could switch to using classNames instead
The buttons need to be inserted into cells, they can't be directly insterted into a row
You can cut this all down quite a bit by using an array of field names to iterate and using TableElement.insertRow() and TableRow.insertCell()
Something like:
const fields = ['studentNo','fname','lname','amount_paid','amount_owing','date_joined','is_owing'] ;
table.forEach((res) => {
const row = dpTable.insertRow();
// loop over fields array to create cell for each
fields.forEach((f) => {
const cell = row.insertCell();
cell.innerText = res[f];
cell.className = 'trContent';
});
const btnCell = row.insertCell();// cell for button
const trash_btn = document.createElement('button');
const trash_Icon = document.createElement('i');
trash_Icon.classList.add('fas', 'fa-trash', 'trash-btn');
trash_btn.append(trash_icon);
btnCell.append(trash_btn);// insert button in own cell
trash_btn.addEventListener('click', .....);
});
It is likely because you're declaring them as const which means they shouldn't be rewritten - but you have them in a loop which reassigns them and so causes an error. You can use let instead in this case
async function loadTable() {
try {
while(dpTable.firstChild) dpTable.removeChild(dpTable.firstChild);
const result = await fetch('http://localhost:5000/users/student_data', { method: 'GET' });
const table = await result.json();
let tr, studentNo, name, surname, date_joined, fees_paid, fees_owing, is_owing, trash_btn, trash_Icon
table.forEach((res) => {
//Create Table elements and then load results into the elements
tr = document.createElement('tr');
studentNo = document.createElement('td');
name = document.createElement('td');
surname = document.createElement('td');
date_joined = document.createElement('td');
fees_paid = document.createElement('td');
fees_owing = document.createElement('td');
is_owing = document.createElement('td');
trash_btn = document.createElement('button');
trash_Icon = document.createElement('i');
tr.id = 'Content';
studentNo.id = res.id;
name.id = 'fname';
surname.id = 'lname';
fees_paid.id = 'amount_paid';
fees_owing.id = 'amount_owing';
is_owing.id = 'debt';
//Enter the data from the response
studentNo.innerText = res.id;
name.innerText = res.fname;
surname.innerText = res.lname;
fees_paid.innerText = res.amount_paid;
fees_owing.innerText = res.amount_owing;
date_joined.innerText = res.date_joined;
is_owing.innerText = res.is_owing;
trash_btn.innerText = 'Delete';
//Add Classes for elements
studentNo.classList.add('trContent');
name.classList.add('trContent');
surname.classList.add('trContent');
fees_paid.classList.add('trContent');
fees_owing.classList.add('trContent');
date_joined.classList.add('trContent');
is_owing.classList.add('trContent');
trash_Icon.classList.add('fas');
trash_Icon.classList.add('fa-trash');
trash_Icon.classList.add('trash-btn');
//Append table row elements to main table
tr.append(studentNo);
tr.append(name);
tr.append(surname);
tr.append(fees_paid);
tr.append(fees_owing);
tr.append(date_joined);
tr.append(is_owing);
tr.append(trash_btn);
//Event Listeners
trash_btn.addEventListener('click', async (e) => {
if (window.confirm('Delete')) {
console.log('trash icon clicked');
const jsonReq = {};
jsonReq.id = res.id;
const result = await fetch('http://localhost:5000/users/student_data', {
method: 'DELETE',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(jsonReq),
});
alert('Deleted Record');
window.location.reload();
}
});
//Append the table as a child to the main element
dpTable.appendChild(tr);
});
} catch (e) {
console.log(`Cant load List ${e}`);
}
}
You can definitely simplify your code and make it more flexible into the process. The JSON data returned will have a set of keys - these can be used to build the HTML output without knowing, in advance, what they are. The beauty of that is the same code can display greatly different datasets very easily.
If you were to use the method create shown below to create the new DOM elements you greatly reduce the code needed.
<?php
#---------------------------------------------------
# to emulate whatever "users/student_data" returns.
# this is dummy data for example only.
#
set_include_path('c:/wwwroot/dbo/');
require 'db-conn-details.php';
require 'mysqli-conn.php';
/*
When we receive the AJAX request, identified by querystring,
prepare some sql statement and execute. Return the full
recordset in JSON format.
*/
if( isset( $_GET['fetch'] ) && $_GET['fetch']=='data' ){
ob_clean();
$sql='select * from `sport` limit 10';
$res=$db->query( $sql );
$json=json_encode( $res->fetch_all( MYSQLI_ASSOC ) );
exit( $json );
}
if( $_SERVER['REQUEST_METHOD']=='DELETE' ){
exit('DELETE');
}
?>
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8' />
<title></title>
<style>
#data{ border:1px solid grey;display:table;padding:1rem;width:100%; }
</style>
<script>
// The endpoint used by the ajax query - in this example it is same page.
const url=location.href; //http://localhost:5000/users/student_data
const create=function(t,a,p){
/*
utility to add a new node with attributes and content.
t=type ~ the DOM node type, defaults to DIV
a=attrbutes to set
p=parent to append new node to.
*/
let el=(typeof(t)=='undefined'||t==null)?document.createElement('div'):document.createElement(t);
let _arr=['innerHTML','innerText','html','text'];
for(let x in a)if(a.hasOwnProperty(x)&&!~_arr.indexOf(x))el.setAttribute(x,a[x]);
if(a.hasOwnProperty('innerHTML')||a.hasOwnProperty('html'))el.innerHTML=a.innerHTML||a.html;
if(a.hasOwnProperty('innerText')||a.hasOwnProperty('text'))el.innerText=a.innerText||a.text;
if(p!=null )typeof(p)=='object'?p.appendChild(el):document.getElementById(p).appendChild(el);
return el;
};
async function loadTable() {
try {
// It was not clear where dbTable was defined - hence declared here
const dpTable=document.querySelector('table#data');
while( dpTable.firstChild ) dpTable.removeChild( dpTable.firstChild );
// As this example targets the same page I needed a way to return only db results - hence using querystring.
const result = await fetch( url + '?fetch=data' );
const json = await result.json();
// the callback that will process the button clicks. The button has a dataset attribute set
// that will use the res.id value from each JSON record.
const clickhandler=async function(e){
if( window.confirm('Delete') ) {
const jsonReq = {
id:this.dataset.id
};
const result = await fetch( url, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify( jsonReq ),
});
console.log( result );
alert('Record deleted');
window.location.reload();
}
};
json.forEach( res => {
// find all the keys from the JSON ~ one of which will be the ID for the record ( res.id )
let fields=Object.keys( res );
// generate new table row
let tr=dpTable.insertRow();
// iterate through all json keys - add new table cell with JSON data.
fields.forEach( field => {
create('td',{
'data-id':field,
text:res[field]
},tr);
});
// add the button for the row and assign listener - also assign the dataset attribute for the record ID.
let bttn=create('button',{ text:'Delete','data-id':res.id }, tr.insertCell() );
bttn.addEventListener( 'click', clickhandler );
// add the icon to the button
create('i',{ 'class':'fas fa-trash trash-btn' }, bttn );
});
} catch(e) {
console.log('Unable to load list: %o',e);
}
}
document.addEventListener('DOMContentLoaded',loadTable);
</script>
</head>
<body>
<table id='data'></table>
</body>
</html>
The sql query shown here in the demo returns the following data:
[
{"id":"1","tag":"3D Archery","description":"a form of archery in which the goal is to strike targets in the shape of animals at unknown distances.","enabled":"1"},
{"id":"2","tag":"3x3","description":"a variation of basketball played on a half court with just three players in each team.","enabled":"1"},
{"id":"3","tag":"Abseiling","description":"an adventure sport where the participants descend a steep formation using a rope. (not really a sport).","enabled":"1"},
{"id":"4","tag":"Acroski","description":"athletes on snow skis perform various choreographed\u00c2\u00a0routines (once called Ski Ballet)","enabled":"1"},
{"id":"5","tag":"Adventure Racing","description":"an event combining two or more endurance disciplines, such as Orienteering, Cross-Country Running, Mountain Biking, Paddling and Climbing. It is also called Expedition Racing.","enabled":"1"},
{"id":"6","tag":"Aerials","description":"a freestyle skiing discipline in which athletes ski along a take-off ramp, then perform various in-air tricks.","enabled":"1"},
{"id":"7","tag":"Aerobatics","description":"sport aerobatics involves aircraft maneuvers such as rolls, loops, stall turns (hammerheads), and tailslides.","enabled":"1"},
{"id":"8","tag":"Acrobatic Gymnastics","description":"team of gymnasts work together to perform acrobatic moves in combination with dance moves.","enabled":"1"},
{"id":"9","tag":"Aerobic Gymnastics","description":"another name for Sport Aerobics.","enabled":"1"},{"id":"10","tag":"Aeromodeling","description":"activity using remotely controlled flying model aircraft (not really a sport).","enabled":"1"}
]
And yielded the following output:
So I am writing a Js script that accepts a number of rows and number of columns from the HTML. This will generate an HTML template for the customer using for loops . A customer should have the ability to select a product that features on the HTML template. this will take up 2 columns on the template instead of one.
The way I am trying to allow the customer to choose where the featured product displays a table.
function featProdDisplay(column, row) {
let body;
body = `<table style = "border-collapse: collapse;">`;
for (let i = 0; i < row; i++) {
body += `<tr>`
for (let j = 0; j < column; j++) {
pos = Number(i) + Number(j);
body += `<td id = "` + pos + `onclick="Selected()">
<div>
</div>
</td>`
};
body += `</tr>`
};
body += `</table>`;
return body;
}
function DisplayTable(column, row) {
body = featProdDisplay(column, row);
document.getElementById("FeatDisplay").innerHTML = body;
}
The way i was thinking is that the customer clicks on a block and drags it down to where the he/she would like it to end. those s will then change color.
So my question is is there any way to get the position or IDs of those s that have been selected as the ID is generated Dynamically and getElementById() requires a preexisting ID? Or should I try a different approach entirely?
Thank you in advance
I have a FireBase realtime DB with some data that I would like to display in HTML grid. Structure of the DB is pretty simple:
What I want to do is to have "id", "First column", "Second column", "Third column" as a column headers and data from DB to be displayed under each respective header.
This is my HTML part:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<!--Firebase SDK code below-->
<script src="/__/firebase/8.2.6/firebase-app.js"></script>
<script src="/__/firebase/8.2.6/firebase-analytics.js"></script>
<script src="/__/firebase/8.2.6/firebase-database.js"></script>
<!-- Initialize Firebase -->
<script src="/__/firebase/init.js"></script>
<script src="firebase.js"></script>
<button onClick="readData()">Load data from FB</button>
<table class="table table-striped" id="table">
<thead>
<tr>
<th>ID</th>
<th>First column</th>
<th>Second column</th>
<th>Third column</th>
</tr>
</thead>
<tbody>
</body>
</html>
JS here:
// Firebase reference
var database = firebase.database();
rootRef = database.ref('/');
itemsRef = database.ref('/Items/');
// Other vars
var pushLabels = ["id", "First column", "Second column", "Third column"];
function readData() {
itemsRef.once('value', function (snapshot) {
var arrayLen = pushLabels.length;
var childKeyArr = [];
var table = document.querySelector('#table tbody');
snapshot.forEach(function (childSnapshot) {
var childKey = childSnapshot.key;
childKeyArr.push(childKey);
});
for (var i = 0; i < childKeyArr.length; i++) {
var row = table.insertRow(-1);
for (var j = 0; j < arrayLen; j++) {
cell = row.insertCell(-1);
};
};
for (var i = 0; i < childKeyArr.length + 1; i++) {
database.ref('/Items/' + i).once('value', function (snapshot) {
snapshot.forEach(function (childSnapshot) {
var noha = childSnapshot.val();
for (var i = 0; i < pushLabels.length; i++) {
cell.innerHTML = noha;
};
});
});
}
});
}
I think it's quite "overcomplex". I was able to create rows based on number of parent items in the DB (1,2,3 as shown on the DB hierarchy screenshots) and for each of the nodes I was able to retrieve the data into snapshot but I don’t know how to project it to the grid so each value is correctly displayed to it’s relevant column.
The part where I put something into cell is obviously wrong in my code - I was just testing different ways.
Any help will be appreciated! Also if there is any better solution, some library that can do that in easier way, I would be happy if you can recommend.
Thanks!!
Is there a fixed number of columns expected, or do you want to to create columns dynamically?
Are your column headers already in the html file or are you looking to make them from the keys in the database? There's column headers in your html file, yet you also have an array storing the same. They also match the keys of each child Item so it looks a little confusing.
Here's a quick example of how you could get the table body, create a new row, create the cells, add the values from the database and then add the row to the table body.
var itemsRef = firebase.database().ref().child("Items");
var tableBody = document.querySelector('#table tbody');
function readData() {
itemsRef.once('value', function (snapshot) {
snapshot.forEach(function (item_snapshot) {
//Create html elements, TRs, TD,s etc.
var row = document.createElement("tr");
var col1 = document.createElement("td");
var col2 = document.createElement("td");
var col3 = document.createElement("td");
var col4 = document.createElement("td");
//Add data to the new elements.
col1.innerText = item_snapshot.key;
col2.innerText = item_snapshot.child("First column").val();
col3.innerText = item_snapshot.child("Second_column").val();
col4.innerText = item_snapshot.child("Third_column").val();
//Append the cells into the row and the row into the table body.
row.appendChild(col1);
row.appendChild(col2);
row.appendChild(col3);
row.appendChild(col4);
tableBody.appendChild(row);
});
});
}
It sounds kinda like you want to have the keys of each child Item to be the column headers if I'm not misunderstanding you, but keep in mind that each child might not have the same nodes within it and you only want to load the headers once, rather than from each child Item.
I am currently working on an inventory management web app where I load a large amount of data on an HTML table by using fetch with async.
async function getNewSummary() {
try {
let groups = await fetch(
"http://localhost:3000/dashboard/summary/groups", {
method: "GET"
}
);
let groupData = await groups.json();
console.log(json);
var table = document.getElementById("grouptable");
for (data in groupData)
table.innerHTML = `<tr>
<td>${groupData[data].groups}</td>
<td>${groupData[data].permission}</td>
</tr>`;
} catch (e) {
console.log(e);
}
}
My Express API returns a large JSON array which I loop through and create table rows from. I expected the browser to stop working when it does that. Is there any other way I can get it done?
I need to create the table and then use it as a DataTable.
Just curious, but I tried loading a pretty large dataset (28795 JSON objects) using your code as an example and it loaded pretty quickly. I adjusted the DOM manipulation to use something besides innerHTML, but other than that it is pretty much the same. Here is the request I made:
main.js
async function getNewSummary() {
try {
const groups = await fetch("https://raw.githubusercontent.com/prust/wikipedia-movie-data/master/movies.json");
const groupData = await groups.json();
console.log('Num documents: ', groupData.length);
console.log('Example document: ', groupData[0]);
const table = document.getElementById("grouptable");
for (data in groupData) {
let tr = document.createElement('tr');
let td = document.createElement('td');
td.textContent = groupData[data].title;
tr.appendChild(td);
table.appendChild(tr);
}
} catch (e) {
console.log(e);
}
}
getNewSummary();
And here is a gif of it working, it only takes about 2 seconds to create all 28,795 table rows. Could the problem be the way you are adding the data to the DOM and not the size of the dataset? Or is you dataset much larger? I've never worked with large amounts of data, so I could be wayyyy off base here, but wanted to try and help if I could.
Here is another version where I click a button that changes the background to red and adds some p tags while it's loading. It definitely hangs, but it's only for a second:
Here's my current iteration.
inside the browser
<script>
function inventoryWorker() {
w = new Worker("js/tableHelper.js");
w.onmessage = function (event) {
DatatableMaker(event.data);
}
}
function DatatableMaker(data) {
$(document).ready(function () {
$('#main').DataTable().destroy();
var events = $("#events");
// Setup text input
$("#main tfoot th").each(function () {
var title = $(this).text();
$(this).html(
'<input class="form-control" type="text" placeholder="Search ' + title +
'" />'
);
});
// DataTable
var table = $("#main").DataTable({
data: data,
paging: true,
lengthChange: true,
searching: true,
ordering: true,
info: true,
scrollX: true,
scrollCollapse: true,
autoWidth: true
});
}
</script>
In a seperate file tableHelper.js
async function getInventory() {
var tableRow = [];
try {
let inventory = await fetch("http://localhost:3000/dashboard/inventory", {
method: "GET"
});
let inventoryData = await inventory.json();
console.log(inventoryData);
for (data in inventoryData) {
var temp = [];
temp.push(
inventoryData[data].ReceivingDate,
// etc
);
tableRow.push(temp);
}
postMessage(tableRow);
} catch (e) {
console.log(e);
}
}
It works better but there is a slight pause when the inventoryWorker function is called
In my off time I've been messing around with API's but now I want to create a sortable table with the JSON information returned from the API call.
I found some code online to help me grab the JSON response and make it into a dynamically generated table, which displays just fine. However, when I try to sort it, it wont work. I've tried tablesorter, datatable and sortable.
Right now, I'm just trying to make it work with Sortable but I believe if it works with Sortable, it will work with the rest.
Also, for clarity sake on Sortable, it works in this code on the line where I set the table attribute:
table.setAttribute('data-sortable', '');
I have also tested it on another hard-coded table just in case, and it works. It can be found here.
Here is my code - Or if you want to view it more clearly, I created a jsFiddle for it here
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/sortable/0.8.0/css/sortable-theme-minimal.css" />
</head>
<body>
<div id="showData"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sortable/0.8.0/js/sortable.js"></script>
<script>
url = 'https://jsonplaceholder.typicode.com/posts';
function foo() {
// RETURN the promise
return fetch(url).then(function(response) {
return response.json(); // process it inside the `then`
});
}
foo().then(function(response) {
// access the value inside the `then`
// EXTRACT VALUE FOR HTML HEADER.
var col = [];
for (var i = 0; i < response.length; i++) {
for (var key in response[i]) {
if (col.indexOf(key) === -1) {
col.push(key);
}
}
}
var container = document.getElementById('showData');
var table = document.createElement('table');
table.setAttribute('data-sortable', '');
container.appendChild(table);
// CREATE HTML TABLE HEADER ROW USING THE EXTRACTED HEADERS ABOVE.
var tr = table.insertRow(-1); // TABLE ROW.
var thead = document.createElement("thead");
table.appendChild(thead);
for (var i = 0; i < col.length; i++) {
var th = document.createElement("th"); // TABLE HEADER.
th.innerHTML = col[i];
thead.appendChild(th);
}
// ADD JSON DATA TO THE TABLE AS ROWS.
for (var i = 0; i < response.length; i++) {
tr = table.insertRow(-1);
for (var j = 0; j < col.length; j++) {
var tabCell = tr.insertCell(-1);
tabCell.innerHTML = response[i][col[j]];
}
}
// FINALLY ADD THE NEWLY CREATED TABLE WITH JSON DATA TO A CONTAINER.
var divContainer = document.getElementById("showData");
divContainer.innerHTML = "";
divContainer.appendChild(table);
})
</script>
</body>
</html>
According to the docs:
If you add tables with JavaScript, call init after they are added to
the page:
Sortable.init()
As you are dynamically generating the table, you need to call Sortable.init() after everything is ready in the DOM.