npm package: @riotjs/dom-bindings
Description: Riot.js DOM bindings
Author: Gianluca Guarini
Latest version: 10.0.5

# dom-bindings

[![Build Status][ci-image]][ci-url]
[![Code Quality][qlty-image]][qlty-url]
[![NPM version][npm-version-image]][npm-url]
[![NPM downloads][npm-downloads-image]][npm-url]
[![MIT License][license-image]][license-url]
[![Coverage Status][coverage-image]][coverage-url]

## Usage

```js
import { template, expressionTypes } from '@riotjs/dom-bindings'

// Create the app template
const tmpl = template('<p> </p>', [
  {
    selector: 'p',
    expressions: [
      {
        type: expressionTypes.TEXT,
        childNodeIndex: 0,
        evaluate: (scope) => scope.greeting,
      },
    ],
  },
])

// Mount the template to any DOM node
const target = document.getElementById('app')

const app = tmpl.mount(target, {
  greeting: 'Hello World',
})
```

[ci-image]: https://img.shields.io/github/actions/workflow/status/riot/dom-bindings/test.yml?style=flat-square
[ci-url]: https://github.com/riot/dom-bindings/actions
[license-image]: http://img.shields.io/badge/license-MIT-000000.svg?style=flat-square
[license-url]: LICENSE
[npm-version-image]: http://img.shields.io/npm/v/@riotjs/dom-bindings.svg?style=flat-square
[npm-downloads-image]: http://img.shields.io/npm/dm/@riotjs/dom-bindings.svg?style=flat-square
[npm-url]: https://npmjs.org/package/@riotjs/dom-bindings
[coverage-image]: https://qlty.sh/gh/riot/projects/dom-bindings/coverage.svg
[coverage-url]: https://qlty.sh/gh/riot/projects/dom-bindings
[qlty-image]: https://qlty.sh/gh/riot/projects/dom-bindings/maintainability.svg
[qlty-url]: https://qlty.sh/gh/riot/projects/dom-bindings

## API

### template(String, Array)

The template method is the most important of this package.
It will create a `TemplateChunk` that could be mounted, updated and unmounted to any DOM node.

<details>
  <summary>Details</summary>

A template will always need a string as first argument and a list of `Bindings` to work properly.
Consider the following example:

```js
const tmpl = template('<p> </p>', [
  {
    selector: 'p',
    expressions: [
      {
        type: expressionTypes.TEXT,
        childNodeIndex: 0,
        evaluate: (scope) => scope.greeting,
      },
    ],
  },
])
```

The template object above will bind a [simple binding](#simple-binding) to the `<p>` tag.

</details>

### bindingTypes

Object containing all the type of bindings supported

### expressionTypes

Object containing all the expressions types supported

## Bindings

A binding is simply an object that will be used internally to map the data structure provided to a DOM tree.

<details>
  <summary>Details</summary>
To create a binding object you might use the following  properties:

- `expressions`
  - type: `Array<Expression>`
  - required: `true`
  - description: array containing instructions to execute DOM manipulation on the node queried
- `type`
  - type: `Number`
  - default:`bindingTypes.SIMPLE`
  - optional: `true`
  - description: id of the binding to use on the node queried. This id must be one of the keys available in the `bindingTypes` object
- `selector`
  - type: `String`
  - default: binding root **HTMLElement**
  - optional: `true`
  - description: property to query the node element that needs to updated

The bindings supported are only of 4 different types:

- [`simple`](#simple-binding) to bind simply the expressions to a DOM structure
- [`each`](#each-binding) to render DOM lists
- [`if`](#if-binding) to handle conditional DOM structures
- [`tag`](#tag-binding) to mount a coustom tag template to any DOM node

Combining the bindings above we can map any javascript object to a DOM template.

</details>

### Simple Binding

These kind of bindings will be only used to connect the expressions to DOM nodes in order to manipulate them.

<details>
  <summary>Details</summary>

**Simple bindings will never modify the DOM tree structure, they will only target a single node.**<br/>
A simple binding must always contain at least one of the following expression:

- `attribute` to update the node attributes
- `event` to set the event handling
- `text` to update the node content
- `value` to update the node value

For example, let's consider the following binding:

```js
const pGreetingBinding = {
  selector: 'p',
  expressions: [
    {
      type: expressionTypes.TEXT,
      childNodeIndex: 0,
      evaluate: (scope) => scope.greeting,
    },
  ],
}

template('<article><p> </p></article>', [pGreetingBinding])
```

In this case we have created a binding to update only the content of a `p` tag.<br/>
_Notice that the `p` tag has an empty text node that will be replaced with the value of the binding expression whenever the template will be mounted_

</details>

#### Simple Binding Expressions

The simple binding supports DOM manipulations only via expressions.

<details>
  <summary>Details</summary>
An expression object must have always at least the following properties:

- `evaluate`
  - type: `Function`
  - description: function that will receive the current template scope and will return the current expression value
- `type`
  - type: `Number`
  - description: id to find the expression we need to apply to the node. This id must be one of the keys available in the `expressionTypes` object

</details>

##### Attribute Expression

The attribute expression allows to update all the DOM node attributes.

<details>
  <summary>Details</summary>
  This expression might contain the optional `name` key to update a single attribute for example:

```js
// update only the class attribute
{ type: expressionTypes.ATTRIBUTE, name: 'class', evaluate(scope) { return scope.attr }}
```

If the `name` key will not be defined and the return of the `evaluate` function will be an object, this expression will set all the pairs `key, value` as DOM attributes. <br/>
Given the current scope `{ attr: { class: 'hello', 'name': 'world' }}`, the following expression will allow to set all the object attributes:

```js
{ type: expressionTypes.ATTRIBUTE, evaluate(scope) { return scope.attr }}
```

If the return value of the evaluate function will be a `Boolean` the attribute will be considered a boolean attribute like `checked` or `selected`...

</details>

##### Event Expression

The event expression is really simple, It must contain the `name` attribute and it will set the callback as `dom[name] = callback`.

<details>
  <summary>Details</summary>
For example:

```js
// add an event listener
{ type: expressionTypes.EVENT, name: 'onclick', evaluate(scope) { return function() { console.log('Hello There') } }}
```

To remove an event listener you should only `return null` via evaluate function:

```js
// remove an event listener
{ type: expressionTypes.EVENT, name: 'onclick', evaluate(scope) { return null } }}
```

</details>

##### Text Expression

The text expression must contain the `childNodeIndex` that will be used to identify which childNode from the `element.childNodes` collection will need to update its text content.

<details>
  <summary>Details</summary>
Given for example the following template:

```html
<p><b>Your name is:</b><i>user_icon</i></p>
```

we could use the following text expression to replace the CommentNode with a TextNode

```js
{ type: expressionTypes.TEXT, childNodeIndex: 2, evaluate(scope) { return 'Gianluca' } }}
```

</details>

##### Value Expression

The value expression will just set the `element.value` with the value received from the evaluate function.

<details>
  <summary>Details</summary>
It should be used only for form elements and it might look like the example below:

```js
{ type: expressionTypes.VALUE, evaluate(scope) { return scope.val }}
```

</details>

### Each Binding

The `each` binding is used to create multiple DOM nodes of the same type. This binding is typically used in to render javascript collections.

<details>
  <summary>Details</summary>

**`each` bindings will need a template that will be cloned, mounted and updated for all the instances of the collection.**<br/>
An each binding should contain the following properties:

- `itemName`
  - type: `String`
  - required: `true`
  - description: name to identify the item object of the current iteration
- `indexName`
  - type: `Number`
  - optional: `true`
  - description: name to identify the current item index
- `evaluate`
  - type: `Function`
  - required: `true`
  - description: function that will return the collection to iterate
- `template`
  - type: `TemplateChunk`
  - required: `true`
  - description: a dom-bindings template that will be used as skeleton for the DOM elements created
- `condition`
  - type: `Function`
  - optional: `true`
  - description: function that can be used to filter the items from the collection

The each bindings have the highest [hierarchical priority](#bindings-hierarchy) compared to the other riot bindings.
The following binding will loop through the `scope.items` collection creating several `p` tags having as TextNode child value dependent loop item received

```js
const eachBinding = {
  type: bindingTypes.EACH,
  itemName: 'val',
  indexName: 'index'
  evaluate: scope => scope.items,
  template: template(' ', [{
    expressions: [
      {
        type: expressionTypes.TEXT,
        childNodeIndex: 0,
        evaluate: scope => `${scope.val} - ${scope.index}`
      }
    ]
  }
}

template('<p></p>', [eachBinding])
```

</details>

### If Binding

The `if` bindings are needed to handle conditionally entire parts of your components templates

<details>
  <summary>Details</summary>

**`if` bindings will need a template that will be mounted and unmounted depending on the return value of the evaluate function.**<br/>
An if binding should contain the following properties:

- `evaluate`
  - type: `Function`
  - required: `true`
  - description: if this function will return truthy values the template will be mounted otherwise unmounted
- `template`
  - type: `TemplateChunk`
  - required: `true`
  - description: a dom-bindings template that will be used as skeleton for the DOM element created

The following binding will render the `b` tag only if the `scope.isVisible` property will be truthy. Otherwise the `b` tag will be removed from the template

```js
const ifBinding = {
  type: bindingTypes.IF,
  evaluate: scope => scope.isVisible,
  selector: 'b'
  template: template(' ', [{
    expressions: [
      {
        type: expressionTypes.TEXT,
        childNodeIndex: 0,
        evaluate: scope => scope.name
      }
    ]
  }])
}

template('<p>Hello there <b></b></p>', [ifBinding])
```

</details>

### Tag Binding

The `tag` bindings are needed to mount custom components implementations

<details>
  <summary>Details</summary>

`tag` bindings will enhance any child node with a custom component factory function. These bindings are likely riot components that must be mounted as children in a parent component template

A tag binding might contain the following properties:

- `getComponent`
  - type: `Function`
  - required: `true`
  - description: the factory function responsible for the tag creation
- `evaluate`
  - type: `Function`
  - required: `true`
  - description: it will receive the current scope and it must return the component id that will be passed as first argument to the `getComponent` function
- `slots`
  - type: `Array<Slot>`
  - optional: `true`
  - description: array containing the slots that must be mounted into the child tag
- `attributes`
  - type: `Array<AttributeExpression>`
  - optional: `true`
  - description: array containing the attribute values that should be passed to the child tag

The following tag binding will upgrade the `time` tag using the `human-readable-time` template.
This is how the `human-readable-time` template might look like

```js
import moment from 'moment'

export default function HumanReadableTime({ attributes }) {
  const dateTimeAttr = attributes.find(({ name }) => name === 'datetime')

  return template(' ', [
    {
      expressions: [
        {
          type: expressionTypes.TEXT,
          childNodeIndex: 0,
          evaluate(scope) {
            const dateTimeValue = dateTimeAttr.evaluate(scope)
            return moment(new Date(dateTimeValue)).fromNow()
          },
        },
        ...attributes.map((attr) => {
          return {
            ...attr,
            type: expressionTypes.ATTRIBUTE,
          }
        }),
      ],
    },
  ])
}
```

Here it's how the previous tag might be used in a `tag` binding

```js
import HumanReadableTime from './human-readable-time'

const tagBinding = {
  type: bindingTypes.TAG,
  evaluate: () => 'human-readable-time',
  getComponent: () => HumanReadableTime,
  selector: 'time',
  attributes: [
    {
      evaluate: (scope) => scope.time,
      name: 'datetime',
    },
  ],
}

template('<p>Your last commit was: <time></time></p>', [tagBinding]).mount(
  app,
  {
    time: '2017-02-14',
  },
)
```

The `tag` bindings have always a lower priority compared to the `if` and `each` bindings

</details>

#### Slot Binding

The slot binding will be used to manage nested slotted templates that will be update using parent scope

<details>
  <summary>Details</summary>
An expression object must have always at least the following properties:

- `evaluate`
  - type: `Function`
  - description: function that will receive the current template scope and will return the current expression value
- `type`
  - type: `Number`
  - description: id to find the expression we need to apply to the node. This id must be one of the keys available in the `expressionTypes` object
- `name`
  - type: `String`
  - description: the name to identify the binding html we need to mount in this node

```js
// slots array that will be mounted receiving the scope of the parent template
const slots = [
  {
    id: 'foo',
    bindings: [
      {
        selector: '[expr1]',
        expressions: [
          {
            type: expressionTypes.TEXT,
            childNodeIndex: 0,
            evaluate: (scope) => scope.text,
          },
        ],
      },
    ],
    html: '<p expr1> </p>',
  },
]

const el = template('<article><slot expr0/></article>', [
  {
    type: bindingTypes.SLOT,
    selector: '[expr0]',
    name: 'foo',
  },
]).mount(
  app,
  {
    slots,
  },
  { text: 'hello' },
)
```

</details>

## Bindings Hierarchy

If the same DOM node has multiple bindings bound to it, they should be created following the order below:

1. Each Binding
2. If Binding
3. Tag Binding

<details>
  <summary>Details</summary>

Let's see some cases where we might combine multiple bindings on the same DOM node and how to handle them properly.

### Each and If Bindings

Let's consider for example a DOM node that sould handle in parallel the Each and If bindings.
In that case we could skip the `If Binding` and just use the `condition` function provided by the [`Each Binding`](#each-binding)
Each bindings will handle conditional rendering internally without the need of extra logic.

### Each and Tag Bindings

A custom tag having an Each Binding bound to it should be handled giving the priority to the Eeach Binding. For example:

```js
const components = {
  'my-tag': function ({ slots, attributes }) {
    return {
      mount(el, scope) {
        // do stuff on the mount
      },
      unmount() {
        // do stuff on the unmount
      },
    }
  },
}
const el = template('<ul><li expr0></li></ul>', [
  {
    type: bindingTypes.EACH,
    itemName: 'val',
    selector: '[expr0]',
    evaluate: (scope) => scope.items,
    template: template(null, [
      {
        type: bindingTypes.TAG,
        name: 'my-tag',
        getComponent(name) {
          // name here will be 'my-tag'
          return components[name]
        },
      },
    ]),
  },
]).mount(target, { items: [1, 2] })
```

The template for the Each Binding above will be created receiving `null` as first argument because we suppose that the custom tag template was already stored and registered somewhere else.

### If and Tag Bindings

Similar to the previous example, If Bindings have always the priority on the Tag Bindings. For example:

```js
const el = template('<ul><li expr0></li></ul>', [
  {
    type: bindingTypes.IF,
    selector: '[expr0]',
    evaluate: (scope) => scope.isVisible,
    template: template(null, [
      {
        type: bindingTypes.TAG,
        evaluate: () => 'my-tag',
        getComponent(name) {
          // name here will be 'my-tag'
          return components[name]
        },
      },
    ]),
  },
]).mount(target, { isVisible: true })
```

The template for the IF Binding will mount/unmount the Tag Binding on its own DOM node.

</details>