List rendering
Using itemize
API
To create the dynamic list inside your component, use itemize
HTML attribute for the list container element in your template:
MyComponent.template = html`
<div itemize="userList">
<template>
<div>First name: {{firstName}}</div>
<div>Second name: {{secondName}}</div>
</template>
</div>`;
The itemize
attribute value should point to the certain key in component's data context:
class MyComponent extends Symbiote {
init$ = {
userList: [
{
firstName: 'John',
secondName: 'Snow',
},
{
firstName: 'Peter',
secondName: 'Sand',
},
],
};
}
Of course, you can use any type of data context token or the computed list property.
For example, inherited:
MyComponent.template = html`
<div itemize="^userList">
...item template
</div>`
Or named:
MyComponent.template = html`
<div itemize="APP/userList">
...item template
</div>`;
More details about data context types, you can find in the Context section.
Computed list example:
class MyComponent extends Symbiote {
init$ = {
rawData: [
{
date: Date.now(),
isVisible: true,
},
{
date: Date.now(),
isVisible: false,
},
],
'+userList': () => this.$.data.filter((item) => {
return item.isVisible;
}),
};
}
MyComponent.template = html`
<div itemize="+userList"> ... </div>
`;
More details about computed properties you can find here.
List items
By default, all the resulting list items - are Symbiote-components.
That means they have all described APIs accessible and you can interact with them same way.
All items are wrapped with a corresponding custom element.
So, if you don't need to have an extra container for your styling purposes, use display: contents
CSS property for such containers.
This property will be added to each item by default, if you don't set the custom tag names for your list items.
To create custom named tag for your list items, use item-tag
attribute:
MyComponent.template = html`
<div itemize="userList" item-tag="user-card">
<template>
<div>{{firstName}}</div>
<div>{{secondName}}</div>
</template>
</div>`;
In this case, you can use that tag as the CSS selector:
user-card {
display: flex;
}
If you planning to add some additional functionality for the each list item, you can pre-define your list item component:
class UserCard extends Symbiote {
init$ = {
firstName: '',
secondName: '',
};
initCallback() {
this.onclick = () => {
alert(`Hello ${this.$.firstName} ${this.$.secondName}!`);
};
}
}
UserCard.template = html`
<div>{{firstName}}</div>
<div>{{secondName}}</div>
`;
UserCard.reg('user-card');
Now, this component can be used as the list item in the other component:
html`
<div itemize="listData" item-tag="user-card"></div>
`;
List item template
By default, each item will be created with a template, taken from container initial inner contents:
html`
<div ${{itemize: 'listDate', 'item-tag': 'my-list-item'}}>
<div>{{firstName}}</div>
<div>{{secondName}}</div>
</div>
`;
Note, that data binding keys will be connected with a fields of each data entry, not the parent component itself.
We recommend to wrap item template into the template
tag each time when you use this approach:
html`
<div ${{itemize: 'listDate', 'item-tag': 'my-list-item'}}>
<template>
<div>{{firstName}}</div>
<div>{{secondName}}</div>
</template>
</div>
`;
It helps browser to ignore some specific tag behavior before the template will be copied as a item contents.
When you using an external component as a list item, template wrapping is not necessary.
Possible data types and structure
The source data for the lists could be an Array
or Object
collections.
Each item descriptor should have a flat structure, like the any standard Symbiote state initiator object.
In case of Object
data collection, all item keys will be reflected for the each item with the _KEY_
property:
class MyComponent extends Symbiote {
init$ = {
userList: {
id1: {
firstName: 'John',
secondName: 'Snow',
},
id2: {
firstName: 'Peter',
secondName: 'Sand',
},
},
};
}
MyComponent.template = html`
<div itemize="userList" item-tag="user-card">
<div>ID: {{_KEY_}}</div>
<div>{{firstName}}</div>
<div>{{secondName}}</div>
</div>
`;
Dynamic updates
To update your list, just set the new data collection:
class MyComponent extends Symbiote {
init$ = {
userList: [],
};
async initCallback() {
this.$.userList = await (await window.fetch('https://<MY-DATA-ENDPOINT>.io')).json();
}
}
If data collection size is constant, you can use the complete data for the initial item rendering, and then to provide changes only:
class MyComponent extends Symbiote {
init$ = {
userList: [
// Initial full data:
{
firstName: 'John',
secondName: 'Snow',
},
{
firstName: 'Peter',
secondName: 'Sand',
},
],
};
initCallback() {
this.$.userList = [
// Updates only:
{
secondName: '<SOME FIXED DATA>',
},
{
secondName: '<SOME FIXED DATA>',
},
];
}
}
The null
or false
in list data value will clear the entire list.
Custom items
Symbiote.js allows to use any custom component as a list item, including raw web-components designed for the maximum performance for the big amount of data.
// Create lightweight web-component for each table row:
class TableRow extends HTMLElement {
set rowData(data) {
data.forEach((cellContent, idx) => {
if (!this.children[idx]) {
this.appendChild(document.createElement('td'));
}
this.children[idx].textContent = cellContent;
});
}
}
window.customElements.define('table-row', TableRow);
// Than render big dynamic table with Symbiote:
class MyTable extends Symbiote {
init$ = {
tableData: [],
}
initCallback() {
window.setInterval(() => {
let data = [];
for (let i = 0; i < 10000; i++) {
let rowData = [
i + 1,
Date.now(),
];
data.push({rowData});
}
this.$.tableData = data;
}, 1000);
}
}
MyTable.rootStyles = css`
table-row {
display: table-row;
}
td {
border: 1px solid currentColor;
}
`;
MyTable.template = html`
<h1>Hello table!</h1>
<table ${{itemize: 'tableData', 'item-tag': 'table-row'}}></table>
`;
MyTable.reg('my-table');
Symbiote.js
itemize
API could be used as a convenient benchmarking tool. You can test your components for performance by adding bunch of them into the large lists and analyzing the result.