Criei um EntityTable (tabela de entidades) HtmlElement personalizado (em sua maioria) que busca e exibe com sucesso dados JSON de um microsserviço RESTful personalizado em um HTML <table>
.
De outro módulo de código ( app.js
), desejo selecionar (e destacar) a primeira linha da tabela ( <tr>
), incluindo o valor do ID da entidade inteira que ela contém em sua primeira célula ( <td>
), e passar a linha (como a linha selecionada padrão) para um ouvinte que usará o ID para preencher uma segunda EntityTable relacionada.
Mas quando defino um ponto de interrupção, app.js
apenas os elementos <link>
da tabela <table>
parecem existir no elemento ShadowRoot da EntityTable, que foi anexado no open
modo em seu constructor()
.
índice.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)
});
Tentei mover a this.shadowRoot.appendChild(table)
instrução para cima, logo abaixo do método await displayData()
da EntityTable this.addEventListener()
, e torná-la await
, mas isso resultou em menos elementos (apenas o <link>
) disponíveis.
Suspeito que o app.js
código continua sendo executado enquanto as fetchData()
promessas `displayData() e `displayData() são aguardadas, mas não sei como evitar isso.
Não sou um cara de front-end. Qualquer ajuda seria muito apreciada.
O Problema
O problema que você está enfrentando não tem nada a ver com o Shadow DOM. Está relacionado à programação assíncrona e à maneira como você está usando eventos. A situação é agravada pela mistura de UI e carregamento de dados em um único componente.
Metodologia Aprimorada
DOMContentLoaded
. Não é relevante.Reformule a EntityTable
EntityTable
duas propriedades:items
- Um conjunto de itens de dados a serem renderizados pela tabela. O padrão énull
. Quando o conjunto de itens estiver definido, renderize a tabela no DOM sombra. Opcionalmente, envie um evento personalizado "items-change".value
- Representa o item de dados selecionado. O padrão énull
. Quandovalue
definido, selecione a linha associada na tabela. Opcionalmente, envie um evento personalizado "change".Isso é tudo o que você precisa no seu componente de tabela básico. Neste nível, extraia completamente o carregamento de dados para que você tenha um componente de interface de usuário puro.
Em seguida, a partir do seu código externo você faria:
items
propriedade naEntityTable
instância.value
.Componente de ordem superior
Se desejar, você pode seguir os três passos anteriores e encapsulá-los em um componente de ordem superior que orquestra a busca dos dados e a configuração dos itens.
EntityTable
internamente em seu próprio Shadow DOM ou que possa ser usado para envolver umEntityTable
Light DOM.loadingState
- Representa o status do processo de carregamento de dados. Pode ter um dos três valores: "não carregado", "carregando" e "carregado". O valor inicial é "não carregado". Opcionalmente, quando o valor for definido como "carregado", envie um evento personalizado "carregado".src
- Quando a origem for alterada, atualizeloadingState
para "carregando" e busque os dados. Quando os dados forem carregados, defina aitems
propriedade naEntityTable
instância e atualizeloadingState
para "carregado".src
valor inicial do atributo emconnectedCallback
e use esse valor para definir a propriedade, iniciando o processo de carregamento (sesrc
houver um valor). Além disso, faça o mesmo emattributeChangedCallback
para que issosrc
possa ser processado na conexão e em alterações de propriedade e atributo.value
eitems
que passem para o orquestradoEntityTable
. Se você criou osEntityTable
eventos bubble e composite, eles passarão automaticamente.