We have some framework agnostic components. They work very well.
<a>
, <p>
, <script>
, <input>
, <svg>
, even the dreaded <table>
.
Imagine a nightmare scenario where <table>
was implemented:
<table
headers="myTableData.headers"
rows="myTableData.rows"
/>
We live in this nightmare scenario.
var CoolTable = require('cool-table-constructor');
new CoolTable({
element: document.querySelector('#my-cool-table'),
headers: [{ /*...*/ }],
rows: [{ /*...*/ }],
});
Framework agnostic components can not take references to data objects. For a component to be portable, it must be declarative and composable.
<cool-table>
<cool-table-head>
Row Header #1
</cool-table-head>
<cool-table-head>
Row Header #2
</cool-table-head>
<cool-table-row>/* etc. and so on */</cool-table-row>
<cool-table-row>/* etc. and so on */</cool-table-row>
<cool-table-row>/* etc. and so on */</cool-table-row>
</cool-table
Any framework can manage those components.
Data flow is the problem. The solution for reusable components is to NOT solve it.
The implementation of <cool-table>
can use some pretty amazing strategies to help keep its children under control no matter how you add/remove them.
1 : getElementsByTagName
gives you an always up-to-date collection of elements by a tag name. It can be scoped to a parent element.
2: Event delegation, we don't have to manage all of our event handlers, just bind on the most logical parent element and delegate away.
class CoolTable extends HTMLElement {
constructor () {
this.headers = this.getElementsByTagName('cool-table-head');
this.rows = this.getElementsByTagName('cool-table-row');
}
attachedCallback () {
this.addEventListener('click', (event) => {
if (event.target.matches('cool-table-head') {
this.sortTable(event);
}
});
}
sortTable (event) {
for (let row of this.rows) {
/* real cool table sorting */
}
}
}
customElements.define('coo-table', CoolTable);