npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@riotjs/dom-bindings

v9.2.5

Published

Riot.js DOM bindings

Downloads

4,161

Readme

dom-bindings

Build Status Code Quality NPM version NPM downloads MIT License Coverage Status

Usage

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',
})

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.

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

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 to the <p> tag.

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.

  • 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 to bind simply the expressions to a DOM structure
  • each to render DOM lists
  • if to handle conditional DOM structures
  • tag to mount a coustom tag template to any DOM node

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

Simple Binding

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

Simple bindings will never modify the DOM tree structure, they will only target a single node. 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:

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. 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

Simple Binding Expressions

The simple binding supports DOM manipulations only via expressions.

  • 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
Attribute Expression

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

// 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. Given the current scope { attr: { class: 'hello', 'name': 'world' }}, the following expression will allow to set all the object attributes:

{ 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...

Event Expression

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

// 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:

// remove an event listener
{ type: expressionTypes.EVENT, name: 'onclick', evaluate(scope) { return null } }}
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.

<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

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

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

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

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.

each bindings will need a template that will be cloned, mounted and updated for all the instances of the collection. 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 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

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])

If Binding

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

if bindings will need a template that will be mounted and unmounted depending on the return value of the evaluate function. 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

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])

Tag Binding

The tag bindings are needed to mount custom components implementations

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

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

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

Slot Binding

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

  • 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
// 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' },
)

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

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 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:

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:

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.