I’ve created a working (mostly) custom EntityTable (entity-table) HtmlElement that successfully fetches and displays JSON data from a custom RESTful microservice in an HTML <table>.

From another code module (app.js) I want to select (and highlight) the first table row (<tr>), including the integer entity id value it contains in its first cell (<td>), and pass the row (as the default selected row) to a listener that will use the id to populate a second, related EntityTable.
But when I set a breakpoint in app.js only the ‘tableelements seem to exist in the EntityTable's ShadowRoot element, which was attached inopenmode in itsconstructor()`.


index.html
<section id="definitions">
<entity-table id="entitytypedefinition" baseUrl="http://localhost:8080/entityTypes/" entityTypeName="EntityTypeDefinition" whereClause="%22Id%22%20%3E%200" sortClause="%22Ordinal%22%20ASC" pageNumber="1" pageSize="20" includeColumns="['Id', 'LocalizedName', 'LocalizedDescription', 'LocalizedAbbreviation']" zeroWidthColumns="['Id']" eventListener="entityTableCreated"></entity-table>
</section>
EntityTable.js
//NOTE: Copyright © 2003-2025 Deceptively Simple Technologies Inc. Some rights reserved. Please see the aafdata/LICENSE.txt file for details.
class EntityTable extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
console.log(`EntityTable constructor ends.`);
}
//NOTE: Called each time this custom element is added to the document, and the specification recommends that custom element setup be performed in this callback rather than in the constructor
connectedCallback() {
const link = document.createElement('link');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('href', 'css/style.css');
this.shadowRoot.appendChild(link);
//TODO: Get live authentication token
document.cookie = "Authentication=XXX";
const table = document.createElement('table');
console.log(`EntityTable connectedCallback() Explicit parent id: ${this.getAttribute('id')}`);
table.setAttribute('id', this.getAttribute('id') + '-23456' || 'entitytypedefinition-34567') //TODO: Generate and append a unique id
table.setAttribute('class', 'entity-table');
console.log(`EntityTable connectedCallback() Not fetching data yet!`);
//TODO: Should this be put back into the constructor???
//TODO: And should the event listener be added to the document or to the table???
this.addEventListener(this.getAttribute('eventListener') || 'DOMContentLoaded', async () => {
console.log('EntityTable connectedCallback() ' + this.getAttribute('eventListener') + ` event listener added.`);
try {
if ((this.getAttribute('eventListener') == 'entityTableCreated') || (this.getAttribute('eventListener') == 'entityTableRowClicked')) {
const data = await fetchData(this.getAttribute('baseUrl') || 'http://localhost:8080/entityTypes/', this.getAttribute('entityTypeName') || 'EntityTypeDefinition', this.getAttribute('whereClause') || '%22Id%22%20%3E%20-2', this.getAttribute('sortClause') || '%22Ordinal%22%20ASC', this.getAttribute('pageSize') || 20, this.getAttribute('pageNumber') || 1);
await displayData(data, table, this.getAttribute('includeColumns') || ['Id', 'EntitySubtypeId', 'TextKey'], this.getAttribute('zeroWidthColumns') || []);
}
}
catch (error) {
console.error('Error fetching data:', error);
}
});
this.shadowRoot.appendChild(table);
console.log(`EntityTable connectedCallback() Data fetched and displayed!`);
}
}
customElements.define('entity-table', EntityTable)
async function fetchData( ...
async function displayData( ...
app.js
//NOTE: Copyright © 2003-2025 Deceptively Simple Technologies Inc. Some rights reserved. Please see the aafdata/LICENSE.txt file for details.
console.log(`app.js executing! No DOMContentLoaded listener added yet.`);
//NOTE: Constructors for both EntityTables defined in index.html are called here
//NOTE: "Wire up" the two EntityTables so that when a row is clicked in the first EntityTable, the data is sent to the second EntityTable
document.addEventListener('DOMContentLoaded', () => {
const entityTableDefinitions = document.getElementById('entitytypedefinition');
const entityTableAttributes = document.getElementById('entitytypeattribute');
console.log(`app.js still executing! DOMContentLoaded listener now added to page.`);
//NOTE: Populate the definitions table with data by adding custom listener and dispatching custom event
const definitionsEvent = new CustomEvent('entityTableCreated', {
detail: { entityTableDefinitions }
});
console.log(`app.js still executing! Dispatching entityTableCreated event ...`);
entityTableDefinitions.dispatchEvent(definitionsEvent);
const selectedRow = entityTableDefinitions.shadowRoot.innerHTML; //NOTE: Get the first row in the table
//.querySelectorAll('tr').length; //NOTE: Get the first row in the table
//.querySelectorAll('table')[0]
//.querySelectorAll('tbody');
//.querySelectorAll('tr')[0]; //NOTE: Get the first row in the table
//NOTE: Populate the attributes table with data by adding custom listener and dispatching custom event
const attributesEvent = new CustomEvent('entityTableRowClicked', {
detail: { selectedRow }
});
console.log(`app.js still executing! Dispatching entityTableRowClicked event: ${selectedRow} ...`);
entityTableAttributes.dispatchEvent(attributesEvent);
//TODO: Add click event listener to each row in the first table??? (unless passing the EntityTable reference is sufficient)
});
I’ve tried moving the this.shadowRoot.appendChild(table) statement up just under the await displayData() in the EntityTable’s this.addEventListener() method and making it await, but this actually resulted in less elements (only the <link>) being available.
I suspect that the app.js code is continuing to execute while the fetchData() and `displayData() promise(s) are awaited, but I’m stuck on how to prevent this.
I’m not a front-end guy. Any help would be greatly appreciated.