Components

Components in Vanillin are a bit like Web Components. You can:

  • instantiate them with <angular-brackets>,
  • pass params: <my-component param1="value" />,
  • attach event listeners: <dispatching-component onclick="console.log(event)">

and much more.

However, the similarity is pretty shallow. Vanillin's components actually are more like functions in JavaScript.

Let's define one:

<function name="panel">
  <h2>A panel</h2>
  <p>Hello world</p>
</function>

Component definition is similar to function definition - in ECMAScript the function definition is called Function Declaration, because it's a statement, not an expression. name attribute is required if the function is defined as one of the elements of DOM tree. There is and exception to this though. Without name it would be a component expression (Function Expression in ECMAScript), but it would be non-idiomatic in Vanillin (in HTML) to be run as IIFE. Therefore it's allowed only if the component is defined using external template, not with inline code.

Element's name - function - was chosen deliberately to resemble ECMAScript.

Let's use it:

<panel></panel>

bind-component

You can also use bind-component (TODO: bind-component-name?), bind-component expects a string value to be passed which will refer to an environment binding name in current scope. Important note: values passed to bind-component are not ECMAScript identifiers, they are values which can be use as keys in JavaScript objects. As a consequence, Vanillin dicrectly looks up the component by provided name.

See an example:

<div bind-component="panel"></div>

In example above, Vanillin looks for panel value inside enclosing environment.

bind-component can be useful if you want to use non-standard HTML elements - like panel - which in some cases may be hoisted up the tree by browser and break your design.

For example, this markup:

<table>
  <thead>
    <foo></foo>
  </thead>
</table>

will be transformed into:

<foo></foo>
<table>
  <thead></thead>
</table>

bind-component-expr

bind-component-expr is similar to bind-component, but, as name suggests, the final name is evaluted from expression passed inside attribute name. Example:

<script>
  const componentName = "panel";
</script>
<div bind-component="componentName"></div>

Parameters

Parameters extend environment used for binding component's body. They are analogous to JavaScript's function parameters. Declaration and usage order doesn't matter which differs from JavaScript. Attributes order cannot be enforced in HTML. For example:

<function name="panel" title="'Untitled'" contents>
  <h2 bind>title</h2>
  <p bind>contents</p>
</function>

Closures

Closures work the same way as in JavaScript - they are lexical, which means the place of definition matters:

<script>
  var componentsCounter = 0;
</script>
<p>How many components were created? <strong bind>componentsCounter</strong></p>
<function name="panel" title="'Untitled'">
  <script>
    componentsCounter = componentsCounter + 1;
  </script>
  <div bind>title</div>
</function>
<panel></panel> <panel title="'Panel1'"></panel> <panel title="document.title"></panel>
<span bind-component="panel" title="'Run component with attribute'"></span>

<strong> will display 4.

Other observations

As a corollary:

  • components can be part of closure, too:

    <function name="menu-item" text> <span bind>text</span> </function>
    <function name="menu" items="[]"> <menu-item for="let item of items" text="item"></menu-item> </function>
    <menu items="['Home', 'Contact', 'About']"></menu>
  • they can be nested:

    <function name="menu" items="[]">
      <function name="menu-item" text> <span bind>text</span> </function>
      <menu-item for="let item of items" text="item"></menu-item>
    </function>
    <menu items="['Home', 'Contact', 'About']"></menu>
  • scoping works properly:

    <function name="outer"> <function name="inner">Hello!</function> </function>
    
    <!-- can see "Hello!" -->
    <outer></outer>
    
    <!-- can't see "Hello!" -->
    <inner></inner>

On the other hand, Vanillin components do not:

  • return values - feasability of such mechianism is not clear, hence not implemented,
  • inherit from Function object, no bind, apply etc. support - simply not implemented so far and not clear if would be useful.

We've discussed inline HTML component definitions. Let's switch to programmatic usage.

defineComponent

TBD.