DocsCookbookCommunityGitHub →

Template syntax

One of the core template mechanics in Symbiote.js - is a native browser HTML parsing via standard DOM API methods. That's the fastest way to create component template instance in object model representation. It might be quite counterintuitive, but in modern browsers innerHTML works faster, than imperative elements structure creation with document.createElement. That's why we use custom tag attribute set to describe template data bindings.

Attribute value syntax based on key/value pairs:

<div 
  set="textContent: myText; style.color: textColor">
</div>
  • Keys and values should be separated with :
  • All key/value pairs should be separated by ;
  • Spaces are optional, you can use them for better readability.
  • As you can see, nested properties are supported: style.color.
  • All keys are native object property names. So, they provide direct access to the DOM API.

Attributes

To bind some property to the own element's HTML-attribute use @ prefix:

<div 
  set="@class: className">
</div>

Contexts

To bind element to some external data context property, use * prefix for property name:

<div 
  set="textContent: *textFromContext">
</div>

Also, to bind element to named (abstract) context, use context name as property prefix separated by slash symbol:

<div 
  set="textContent: profile/name">
</div>

More information about data context you can find in "Data context" section.

Handlers

Action handler binding is the same as own property:

<input type="text" set="oninput: onTextInput" />

Text nodes

Binding to the text nodes is also supported with the same property scheme:

Local component's data:
<div>Hello {{userName}}! Welcome to the {{currentPlace}}!</div>

or common context data:
<div>Hello {{*userName}}!</div>

or data from some named context:
<div>Hello {{profile/name}}!</div>

Type casting

Inversion:

Local property:
<div set="@hidden: !innerText">{{innerText}}</div>

Common context property:
<div set="@hidden: !*innerText">{{*innerText}}</div>

Named context property:
<div set="@hidden: !app/innerText">{{app/innerText}}</div>

Cast to boolean:

<div set="@contenteditable: !!innerText">{{innerText}}</div>

Alternate binding description syntax

As it was mentioned before, Symbiote.js using HTML-templates which are can be processed by the browser "as is", without any pre-processing. That allows to process templates with a native DOM API in that moment, when we need it. Also we can generate such templates with any frontend or backend tool able to generate HTML. That's why we using syntax based on what native browser parser allows us to do.

It is possible to use separate attributes do describe data bindings:

<h1 set
  -text-content="title"
  -@class="titleClassName">
</h1>

<button set
  -onclick="onButtonClick">
</button>

<div set
  -inner_html="contentHtml">
</div>

In case of using separate attributes you need to use a set attribute to initiate element's attributes processing. As you can see in provided example, binding attributes should be prefixed with - symbol and transformed into the kebab-case (textContent becomes -text-content). For the element properties containing upper-case parts, such as innerHTML, you can use snake-case (innerHTML becomes -inner_html). That's because native HTML-attributes are not case sensitive and you cannot use direct property names in HTML.

Slots

Slots allow you to define placeholders in your template that can be filled with any external markup fragment.

Symbiote.js make slots available without Shadow DOM usage.

Default template slot:

<div class="my-wrapper">
  <slot></slot>
</div>

Named template slots:

<header>
  <slot name="header"></slot>
</header>
<article>
  <slot name="article"></slot>
</article>
<footer>
  <slot name="footer"></slot>
</footer>

Element references

If you need an element reference somewhere in your code logics, use ref attribute for your template element:

<div class="layout">
  <div ref="div1"></div>
  <div ref="div2"></div>
</div>

Reference name should be unique for each element (like an element's id). Then you can use ref collection to get those elements in your code without any additional search:

class MyComponent extends BaseComponent {
  initCallback() {
    this.ref.div1.contenteditable = true;
    this.ref.div2.style.color = 'red';
  }
}

Data based templates

Efficient conditional and data based template rendering is very case specific. In some cases the best solution is to use simple innerHTML approach. HTML parsing is very fast in modern browsers and frequently that's the most performant and convenient way:

class MyComponent extends BaseComponent {
  init$ = {
    listHtml: '',
  }

  initCallback() {
    this.sub('*list', (/** @type {{uid:string}[]} */ list) => {
      this.$.listHtml = list.reduce((html, item) => {
        return html += /*html*/ `<list-item uid="${item.uid}"></list-item>`;
      }, '');
    });
  }
}

MyComponent.template = /*html*/ `
<h2>Items:<h2>
<div class="list-wrapper" set="innerHTML: listHtml"></div>
`;

Dynamic lists

Symbiote.js is supporting dynamic lists rendering and the performant data reconciliation with an unkeyed approach. Take a look at the "List rendering" section for more information.