Programmatically create HTML table from D3 nested JSON object - javascript

I'm trying to make tables for a dashboard using React/D3. One of them is similar to this HTML hard-coded table which I'm trying to make:
table, td{
border: 1px solid black;
}
<table>
<thead>
<td>Location</td>
<td>State</td>
<td># Jobs</td>
</thead>
<tbody>
<tr>
<td rowspan="2">Cell 1 spanning if we have multiple values for this key</td>
<td>SUCCESSFUL</td>
<td>75</td>
</tr>
<tr>
<td>FAILED</td>
<td>22</td>
</tr>
</tbody>
</table>
<br />
Data
The data is retrieved from a SQL-Server database through node-mssql and express and then stored in react state, I pull 7 days worth of data and filter this down to 1 day per component in my react app as i need it.
It returns as normal and has NO nesting in it e.g.
{
"location": "Ireland",
"state": "Finished",
"finish_time": "2018-10-18T12:00:00",
"seconds_passed": 30
}
D3
With D3 I aggregated this data so that, it would be aggregated by location, then by state and then a count of the rows in that aggregation. I achieved this with nest and rollup:
dailyJobStatusCounts = () => {
return (
nest()
.key( row => row.location)
.key( row => row.state)
.rollup(function(values) { return values.length; })
.entries(this._filterTwentyFourHours())
);
}
This returns an object that looks like this:
[{"key":"Ireland","values":[{"key":"SUCCEEDED","value":14},{"key":"FAILED","value":7}]}]
Rendering to HTML in React
My issue occurs around my lack of understanding of how to render this nested JSON object as a HTML table. When attempting to render, I have tried two variations:
React
<table>
<thead>
<tr>
<th>Location</th>
<th>State</th>
<th># Jobs</th>
</tr>
</thead>
<tbody>
{data.map(row => {
return <tr>
<td>{row.key}</td>
{ row.values.map( v => {
return <React.Fragment>
<td>{v.key}</td>
<td>{v.value}</td>
</React.Fragment>
})}
</tr>
})}
</tbody>
</table>
Which renders the table like this:
HTML Output
table, td {
border: 1px solid black;
}
<table>
<thead>
<tr>
<th>Location</th>
<th>State</th>
<th>Jobs</th>
</tr>
</thead>
<tbody>
<tr>
<td>ie11</td>
<td>SUCCEEDED</td>
<td>14</td>
<td>FAILED</td>
<td>7</td>
</tr>
</tbody>
</table>
Or the other variation:
React
<table>
<thead>
<tr>
<th>Location</th>
<th>State</th>
<th># Jobs</th>
</tr>
</thead>
<tbody>
{data.map(row => {
return <tr>
<td>{row.key}</td>
{ row.values.map( v => {
return <tr>
<td>{v.key}</td>
<td>{v.value}</td>
</tr>
})}
</tr>
})}
</tbody>
</table>
HTML Output
table, td {
border: 1px solid black;
}
<table>
<thead>
<tr>
<th>Location</th>
<th>State</th>
<th># Jobs</th>
</tr>
</thead>
<tbody>
<tr>
<td>ie11</td>
<tr>
<td>SUCCEEDED</td>
<td>13</td>
</tr>
<tr>
<td>FAILED</td>
<td>7</td>
</tr>
</tr>
</tbody>
</table>
I see that some of the HTML being output for table is not valid as I have nested values, however I am struggling to get my head around how I can map this JSON object to display the HTML table with the rowspan mentioned at the beginning of this question. Thanks in advance for any answers.
Looked at a somewhat similar SO question which is close, but I'm not sure it answers my use-case here

Related

Group column with the same subheader name in datatable using Javascript/jQuery

I have a table like this
In which I have different city where we have demand-supply of different products.
Now what I want as here demand is different for all the products However supply is the same on all of three product, so I want that table looks like in this manner.
What I want to do is I want only to show the supply column once in the last of the table. This has to be done dynamically as in the future we have multiple products
Can anyone help me with this?
What the code below does is:
Identify the positions of the "Supply"'s and store them in ind array, in this case will be [3, 5, 7]
Loops through ind except for the last element 7(as one "Supply" will be left) and hide all td's; $("td:nth-child("3"), $("td:nth-child("5")
The "Demand"s that precede each of these elements will be assigned two spaces.
let ind = [];
$("td:contains('Supply')").each(function (index) {
ind.push($(this).index() + 1);
});
$(".hide").on("click", function () {
for (let i = 0; i < ind.length - 1; i++) {
let el = $("td:nth-child(" + ind[i] + ")");
el.prev().attr("colspan", "2");
el.hide();
}
});
table,
th,
td {
border: 1px solid black;
border-collapse: collapse;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table>
<tr>
<th>City</th>
<th colspan="2">Product 1</th>
<th colspan="2">Product 2</th>
<th colspan="2">Product 3</th>
</tr>
<tr>
<td></td>
<td>Demand</td>
<td>Supply</td>
<td>Demand</td>
<td>Supply</td>
<td>Demand</td>
<td>Supply</td>
</tr>
<tr>
<td>City 1</td>
<td>50$</td>
<td>60$</td>
<td>90$</td>
<td>60$</td>
<td>100$</td>
<td>60$</td>
</tr>
<tr>
<td>City 2</td>
<td>50$</td>
<td>60$</td>
<td>90$</td>
<td>60$</td>
<td>100$</td>
<td>60$</td>
</tr>
<tr>
<td>City 3</td>
<td>50$</td>
<td>60$</td>
<td>90$</td>
<td>60$</td>
<td>100$</td>
<td>60$</td>
</tr>
<tr>
<td>City 4</td>
<td>50$</td>
<td>60$</td>
<td>90$</td>
<td>60$</td>
<td>100$</td>
<td>60$</td>
</tr>
</table>
<button class="hide">Hide</button>

Creating a custom table row

I am trying to create a custom table row but having difficulty getting it to behave properly. I've tried the two below methods and they give bizarre results. I realize that this is very easy to to without custom elements but this is a small example of a much larger project. What can I change to achieve the desired result?
class customTableRow extends HTMLElement {
constructor(){
super();
var shadow = this.attachShadow({mode: 'open'});
this.tableRow = document.createElement('tr');
var td = document.createElement('td');
td.innerText = "RowTitle";
this.tableRow.appendChild(td);
var td2 = document.createElement('td');
td2.innerText = "RowContent";
td2.colSpan = 4;
this.tableRow.appendChild(td2);
shadow.appendChild(this.tableRow);
}
}
customElements.define('custom-tr', customTableRow);
//Attempt 2
var newTr = new customTableRow;
document.getElementById('table2Body').appendChild(newTr);
td {
border: 1px solid black;
}
<span>Attempt 1:</span>
<table>
<thead>
<tr>
<th>One</th>
<th>Two</th>
<th>Three</th>
<th>Four</th>
<th>Five</th>
</tr>
</thead>
<tbody>
<custom-tr />
</tbody>
</table>
<hr>
<span>Attempt 2:</span>
<table id="table2">
<thead>
<tr>
<th>One</th>
<th>Two</th>
<th>Three</th>
<th>Four</th>
<th>Five</th>
</tr>
</thead>
<tbody id="table2Body">
<!-- It should append here -->
</tbody>
</table>
<hr>
<span>This is how I want it to look:</span>
<table id="table2">
<thead>
<tr>
<th>One</th>
<th>Two</th>
<th>Three</th>
<th>Four</th>
<th>Five</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row Title</td>
<td colspan="4">Row Content</td>
</tbody>
</table>
A <table> element and its subcomponents <tbody>, <tr> require a very specific syntax. For example, only <tr> elements are authorized as children of <tbody>.
Therefore you cannot define a element and insert it in <tbody> or <table>. If you do that it will be moved outside of the <table> at parsing. Hence the display of your first example (look the code in the Dev Tools).
Instead you should define a customized tag instead like in this answer to a similar question.
Or you should redefine a complete custom table structure with <custom-table>, <custom-tbody>... like in this other answer.
Also, you should use closing tag <custom-tr></custom-tr>, and insert your CSS rule in the Shadow DOM if you want it to by applied inside it.

How to create table with multiline cells in React-bootstrap?

I want to create table where some cells contain several lines.
It's work if I do it:
<Table bordered>
<thead>
<tr>
<th>Date</th>
<th>Analysed ID</th>
<th>Analysed Name</th>
<th>Solve To change</th>
</tr>
</thead>
<tbody>
<tr>
<td rowSpan="3">Date</td>
</tr>
<tr>
<td>ID</td>
<td>Name</td>
<td>Decision</td>
</tr>
<tr>
<td>ID</td>
<td>Name</td>
<td>Decision</td>
</tr>
</tbody>
</Table>
I got it:
Table with multiline cell
And now I want to add my 3 "TR" tags in one component, because after I want use for-cycle to create many such components. But components must return content in one closed tag. I tried to contain my 3 "tr" in one parent "tr", but I got error. What can I do here?
It is not possible to create a React Component that returns three elements without wrapping them in another element, such as a div. Otherwise, you'll get the following error:
A valid ReactComponent must be returned. You may have returned undefined, an array or some other invalid object.
Your case here is a bit special, because you cannot have div's as the immediate child of table or tbody, so that's a problem...
What you can do however, is to create a class function that returns an array. Like this:
class MyApp extends React.Component {
getTr = () => {
return [
<tr key={0}>
<td rowSpan="3">Date</td>
</tr>,
<tr key={1}>
<td>ID</td>
<td>Name</td>
<td>Decision</td>
</tr>,
<tr key={2}>
<td>ID</td>
<td>Name</td>
<td>Decision</td>
</tr>
];
}
render() {
return (
<table className="table">
<thead>
<tr>
<th>Date</th>
<th>Analysed ID</th>
<th>Analysed Name</th>
<th>Solve To change</th>
</tr>
</thead>
<tbody>
{this.getTr()}
{this.getTr()}
{this.getTr()}
</tbody>
</table>
);
}
}
ReactDOM.render(<MyApp />, document.getElementById("app"));
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
You need to include tr tags in one div tag.
The right way to rowSpan is this:
var MyRow = React.createClass({
render: function () {
return (
<div>
<tr>
<td rowSpan="2">{this.props.item.date}</td>
<td>{this.props.item.data[0].id}</td>
<td>{this.props.item.data[0].name}</td>
<td>{this.props.item.data[0].solve}</td>
</tr>
<tr>
<td>{this.props.item.data[1].id}</td>
<td>{this.props.item.data[1].name}</td>
<td>{this.props.item.data[1].solve}</td>
</tr>
</div>
);
}
});
This is my working example: http://jsfiddle.net/andrea689/e33pd14L/

Changing the <td> element of a table using DOM

I have been learning about DOMs lately and have been stuck on a problem for days. For some reason, I cannot change the contents of a html table. I have been looking at w3 schools HTML and using DOM to change the table element.
Here is the code for the table :
<div id="courseSummaryContainer" class="tab">
<table cellspacing="0" class="summaryTable courseSummary smallFontTable" summary="Health Care by province">
<thead><tr>
<th>Name</th>
<th>Description</th>
<th>Date of entry</th>
<th>Rating</th>
<th>Grade</th>
<th>Submission entry type</th>
</tr></thead>
<tbody>
<tr class="row-even">
<td>Illinois</td>
<td>Online system</td>
<td>201602</td>
<td>0100</td>
<td>5</td>
<td>Final</td>
</tr>
<tr class="row-odd">
<td>Alabama</td>
<td>Regional area health</td>
<td>201606</td>
<td>0100</td>
<td>7</td>
<td>Final</td>
</tr>
<tr class="row-even">
<td>Illinois</td>
<td>Online system</td>
<td>201602</td>
<td>0100</td>
<td>5</td>
<td>Final</td>
</tr>
</tbody>
</table>
</div>
What I have been trying to do is change the names of the states and their values. To do so, I have been trying to access the <td> element.
To change the contents, I tried the following:
Say for example want to change "Illinois" to "Georgia" I tried the following
document.getElementById("table.summaryTable.courseSummary.sm‌allFontTable").rows[‌2].cells;
x[1].innerHTML = "Georgia";
I am not sure what I am doing wrong however the console keeps giving errors all the time stating the values are null.
Can somebody please offer their guidance?
Use document#querySelector. In this case a simple selector can be .row-even > td:first-child because you only have one .row-even.
How can you be more specific?
If you've got multiple .row-even, by using tbody > tr:nth-child(1) > td:first-child.
If you have multiple tables with .row-even, you can add the id of the container #courseSummaryContainer .row-even > td:first-child or the class of the table .courseSummary .row-even > td:first-child.
var td = document.querySelector('.row-even > td:first-child');
td.innerText = 'Georgia';
<div id="courseSummaryContainer" class="tab">
<table cellspacing="0" class="summaryTable courseSummary smallFontTable" summary="Health Care by province">
<thead><tr>
<th>Name</th>
<th>Description</th>
<th>Date of entry</th>
<th>Rating</th>
<th>Grade</th>
<th>Submission entry type</th>
</tr></thead>
<tbody>
<tr class="row-even">
<td>Illinois</td>
<td>Online system</td>
<td>201602</td>
<td>0100</td>
<td>5</td>
<td>Final</td>
</tr>
<tr class="row-odd">
<td>Alabama</td>
<td>Regional area health</td>
<td>201606</td>
<td>0100</td>
<td>7</td>
<td>Final</td>
</tr>
</tbody>
</table>
</div>
You are not getting the right element in the DOM, if you are going to use the function: "getElementById", you need to pass it the id of the element that you want to have, like:
html:
<div id="courseSummaryContainer" class="tab">
<table id="myTable" cellspacing="0" class="summaryTable courseSummary smallFontTable" summary="Health Care by province">
//... Table content
</table>
</div>
js:
document.getElementById("myTable").rows[‌2].cells;

Javascript <tbody> swapping not working

I've got a couple tables whose content should change based on clicking certain buttons (in this case, links). I've used this Javascript code elsewhere successfully, though with only one parameter in the switchid() function (there was only one table to mess around with). I keep researching examples of this and I seem to be passing the variables correctly, so what am I doing wrong? This code doesn't work on Chrome or IE:
Edit: Per the comments, I was able to whittle my javascript section down to a single, smaller function, that should do the same thing. I have made the change below. It still doesn't work, though.
I also changed my "array" and "x" variables to "JonArray" and "JonX" to avoid any chances of one of those being a reserved word.
<!DOCTYPE html>
<html>
<body>
<script type="text/javascript">
var topTable = new Array('English','Spanish');
var bottomTable = new Array('Japanese','Italian');
function switchid(JonArray,JonX) {
for(var i=0;i<JonArray.length();i++) {
document.getElementById(JonX).style.display='none';
}
document.getElementById(JonX).style.display='table-row-group';
}
</script>
<table border='1'>
<thead>
<tr><td>Odds</td><td>Evens</td></tr>
</thead>
<tfoot>
<tr><td>English</td><td>Spanish</td></tr>
</tfoot>
<tbody id='English'>
<tr><td>One</td><td>Two</td></tr>
<tr><td>Three</td><td>Four</td></tr>
</tbody>
<tbody id='Spanish' style="display:none;">
<tr><td>Uno</td><td>Dos</td></tr>
<tr><td>Tres</td><td>Quatro</td></tr>
</tbody>
</table>
<br />
<table border='1'>
<thead>
<tr><td>Odds</td><td>Evens</td></tr>
</thead>
<tfoot>
<tr><td>Japanese</td><td>Italian</td></tr>
</tfoot>
<tbody id='Japanese'>
<tr><td>Ichi</td><td>Ni</td></tr>
<tr><td>San</td><td>Shi</td></tr>
</tbody>
<tbody id='Italian' style="display:none;">
<tr><td>Un</td><td>Due</td></tr>
<tr><td>Tre</td><td>Quattro</td></tr>
</tbody>
</table>
</body>
</html>
http://jsfiddle.net/92ZPM/1/
I made sure the function and variables were available regardless of when they are created.
I also gave your variables descriptive names, cleaned up and stored the table data in a single object.
JavaScript
window.switchid = function (table, language) {
var tables = {
'top': ['English', 'Spanish'],
'bottom': ['Japanese', 'Italian']
};
for (var i = 0; i < tables[table].length; i++) {
document.getElementById(tables[table][i]).style.display = 'none';
}
document.getElementById(language).style.display =
'table-row-group';
}
HTML
<table border='1'>
<thead>
<tr>
<td>Odds</td>
<td>Evens</td>
</tr>
</thead>
<tfoot>
<tr>
<td>English
</td>
<td>Spanish
</td>
</tr>
</tfoot>
<tbody id='English'>
<tr>
<td>One</td>
<td>Two</td>
</tr>
<tr>
<td>Three</td>
<td>Four</td>
</tr>
</tbody>
<tbody id='Spanish' style="display:none;">
<tr>
<td>Uno</td>
<td>Dos</td>
</tr>
<tr>
<td>Tres</td>
<td>Quatro</td>
</tr>
</tbody>
</table>
<br />
<table border='1'>
<thead>
<tr>
<td>Odds</td>
<td>Evens</td>
</tr>
</thead>
<tfoot>
<tr>
<td>Japanese
</td>
<td>Italian
</td>
</tr>
</tfoot>
<tbody id='Japanese'>
<tr>
<td>Ichi</td>
<td>Ni</td>
</tr>
<tr>
<td>San</td>
<td>Shi</td>
</tr>
</tbody>
<tbody id='Italian' style="display:none;">
<tr>
<td>Un</td>
<td>Due</td>
</tr>
<tr>
<td>Tre</td>
<td>Quattro</td>
</tr>
</tbody>
</table>
you have to change your javascript a little:
var tables=new Array();
tables['topTable'] = new Array('English','Spanish');
tables['bottomTable'] = new Array('Japanese','Italian');
function switchid(JonArray,JonX) {
//alert(JonArray);
var tmptable=tables[JonArray];
for(var i=0;i < tmptable.length;i++) {
document.getElementById(tmptable[i]).style.display='none';
}
document.getElementById(JonX).style.display='';
}
Some of these answers work, but I just caught the REAL answer via Chrome DevTools! In my 'for' loop I was using 'length()' instead of 'length'!!!
Why not to use CSS instead of looping through IDs?
JSFiddle
HTML
<table border='1' class="English">
<thead>
<tr><td>Odds</td><td>Evens</td></tr>
</thead>
<tfoot>
<tr><td onclick="changeLang(this,'English')">English</td><td onclick="changeLang(this,'Spanish')">Spanish</td></tr>
</tfoot>
<tbody id='English'>
<tr><td>One</td><td>Two</td></tr>
<tr><td>Three</td><td>Four</td></tr>
</tbody>
<tbody id='Spanish'>
<tr><td>Uno</td><td>Dos</td></tr>
<tr><td>Tres</td><td>Quatro</td></tr>
</tbody>
</table>
CSS
tbody {
display: none;
}
.English #English, .Spanish #Spanish, .Japanese #Japanese, .Italian #Italian {
display: table-row-group;
}
JS
function changeLang(cell, lang) {
cell.parentNode.parentNode.parentNode.className = lang;
}

Categories