Creating a custom table row - javascript

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.

Related

Webcomponents based on <table>. Slot out of flow

I'm try create data-grid with "web component" based on TABLE. I'm use slot in TBODY to append new items. But TR rows rendered out of TBODY. I don't understand this behavior.
shadowDom disallow use TABLE and TR elements, possibly the reason is this, but using HTMLElement.appendChild() also doesn't work
<html>
<head>
<script type="module">
class DataGrid extends HTMLElement {
TEMPLATE_ID = '#data-grid';
constructor() {
super();
this._records = [];
}
connectedCallback() {
let shadow = this.attachShadow({mode: 'open'});
this._render(shadow);
}
_render(shadow){
let tmpl = document.querySelector(this.TEMPLATE_ID);
shadow.appendChild(tmpl.content.cloneNode(true));
//this.appendChild(tmpl.content.cloneNode(true)); //
}
}
customElements.define('data-grid', DataGrid);
</script>
</head>
<body>
<template id="data-grid">
<table border="1">
<thead>
<tr>
<th>id</th>
<th>time</th>
<th>voltage</th>
<tr>
</thead>
<tbody>
<slot></slot>
<tbody>
</table>
</template>
<data-grid>
<!-- need replaced to component
<grid-row time="" voltage="">
-->
<tr>
<td>1</td>
<td>123123123120</td>
<td>12.0</td>
</tr>
<tr>
<td>2</td>
<td>123123133324</td>
<td>12.1</td>
</tr>
<tr>
<td>1</td>
<td>123123122330</td>
<td>12.2</td>
</tr>
</data-grid>
</body>
From the <TR> documentation on MDN:
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr
Permitted parents
<table> (only if the table has no child <tbody> element, and even
then only after any <caption>, <colgroup>, and <thead>
elements); otherwise, the parent must be <thead>, <tbody> or
<tfoot>
So
<data-grid>
<tr>
is not valid HTML
move (invalid) lightDOM to a <table> inside <data-grid> shadowDOM
note: attachShadow() both sets and returns this.shadowRoot for free
No need to create your own shadow variable.
psuedo code:
const table = this.shadowRoot.querySelector("TABLE");
this.querySelectorAll("TR").forEach(table.appendChild);

How to modify a HTML <td> (without any unique properties) element using Javascript

I'm trying to modify a element using JS however this element does not have any unique properties like ID. Also the table in which this element resides does not have a unique class. Also, the HTML page has multiple tables and td elements.
For example:
Existing HTML :
<table border="1">
<tbody>
<tr>
<td>Id</td>
<td>Name</td>
</tr>
<tr>
<td>12334567</td>
<td>BirthName</td>
</tr>
</tbody>
</table>
I'm trying to modify the cell which contains the value "BirthName" to "BirthName (Sidharth)"
Something Like this:
<table border="1">
<tbody>
<tr>
<td>Id</td>
<td>Name</td>
</tr>
<tr>
<td>12334567</td>
<td>BirthName (Sidharth)</td>
</tr>
</tbody>
</table>
You can find all having BirthName by using bellow colde
const allTds = document.querySelectorAll('td')
// Find the td element that contains the text "BirthName"
const birthDateTd = Array.from(allTds).filter(td=>td.textContent==='BirthName')
After that you can target that <td> as you want.
You can do checking the text for all td and change where matches birthname
let element = document.querySelectorAll('td');
for(let i = 0; i<element.length; i++){
if(element[i].innerText == 'BirthName'){
element[i].innerText += '(Sidharth)';
}
}
If the text is unique then you can use Xpath as shown below and change it.
var td = document.evaluate("//td[contains(text(), 'BirthName')]", document, null, XPathResult.ANY_TYPE, null );
var thisTd = td.iterateNext();
thisTd.innerHTML = "BirthName (Sidharth)";
<table border="1">
<tbody>
<tr>
<td>Id</td>
<td>Name</td>
</tr>
<tr>
<td>12334567</td>
<td>BirthName</td>
</tr>
</tbody>
</table>

Programmatically create HTML table from D3 nested JSON object

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

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;

Categories