atril

Experimental JS rendering library

Ideas from ReactJS, Polymer, Angular 2, Aurelia, made simple

Attribute

A custom attribute changes how the element behaves in the real DOM.

Attributes are powerful. One- and two-way databinding in atril is implemented entirely with attributes, with no special treatment from the core engine.

When using a custom attribute, you have to "opt in" by adding a dot to the name. You can also add an optional hint after the dot to customise its behaviour, like value in bind.value. Most built-ins support various hints. This makes custom attributes very flexible and ensures no conflict with other attributes.

Basics

Here's an example attribute. This is the entire implementation of the built-in class.* binding.

import {Attribute, assign} from 'atril';

@Attribute({attributeName: 'class'})
class Ctrl {
  // Autoassigned by the library.
  @assign element: Element;
  @assign hint: string;
  @assign expression: Function;
  @assign scope: any;

  onPhase() {
    let result = this.expression(this.scope);
    if (result) this.element.classList.add(this.hint);
    else this.element.classList.remove(this.hint);
  }
}
<div let.checked="true">
  <!-- Set class `info` when `checked` is true
       and `error` otherwise. -->
  <label class.info="checked" class.error="!checked">

    <input twoway.checked="checked" type="checkbox">

    <span>I'm checked: {{checked}}</span>

  </label>
</div>

Contextual Dependencies

The library uses a variant of dependency injection — dependency assignment — to give you contextual dependencies for each attribute controller. To get hold of them, use the @assign decorator (ES7/TypeScript) or the static assign property on the constructor function (ES5).

A custom attribute has the following contextual dependencies:

Example:

import {Attribute, assign} from 'atril';

@Attribute({attributeName: 'my-attr'})
class Ctrl {
  @assign element: Element;
  @assign attribute: Attr;
  @assign hint: string;
  @assign expression: Function;
  @assign scope: any;
  @assign vm: any;

  constructor() {
    // <hello-world my-attr.calc="2 + 2"></hello-world>
    console.log(element);
    // my-attr.calc="2 + 2"
    console.log(attribute);
    // 'calc'
    console.log(hint);
    // function that returns 4
    console.log(expression);
    // outer viewmodel or null
    console.log(scope);
    // hello-world's viewmodel
    console.log(vm);
  }
}
<hello-world my-attr.calc="2 + 2"></hello-world>
var Attribute = require('atril').Attribute;

Attribute({attributeName: 'my-attr'})(function() {
  function Ctrl() {}

  // Property names to the left, dependency tokens to the right.
  Ctrl.assign = {
    element: 'element',
    attribute: 'attribute',
    hint: 'hint',
    expression: 'expression',
    scope: 'scope',
    vm: 'vm'
  };

  return Ctrl;
}());

Lifecycle

An attribute's life begins with a constructor call. In addition, it can define two lifecycle methods: onPhase and onDestroy.

This is called whenever the library reflows the tree of components and bindings in response to user activity. For an example, see the class.* implementation above.

When the root of this virtual DOM branch is irrevocably removed from the hierarchy, this method is invoked on all components, attributes, and molds. You can use this as a chance to free memory or perform other cleanup tasks. Example:

class Ctrl {
  constructor() {
    createWastefulResource();
  }

  onDestroy() {
    deleteWastefulResource();
  }
}
Overview Quickstart Component Attribute Mold Databinding Bootstrapping if for let on class ref demo