Quicklinks
Synonymous with custom element. Combines a view model (a data layer) with a view (an HTML template used for every such element).
The viewmodel class describes how to create a viewmodel object (VM for short) for each of these elements. The VM's data and methods are available in the view. It acts as the view's local scope — a feature missing from the native DOM API.
A custom element is registered under a new tag name, and activated during
bootstrapping. Afterwards, atril
manages the element,
automatically updating the view whenever the data changes.
Example custom element:
// Viewmodel.
import {Component} from 'atril';
@Component({
tagName: 'hello-world'
})
class ViewModel {
name = 'world';
static viewUrl = 'hello-world/hello-world.html';
}
<!-- Template. -->
<!-- Updates automatically -->
<h1>Hello, {{name}}!</h1>
<!-- Two-way databinding -->
<input twoway.value="name">
<!-- One-way databinding with manual feedback -->
<input bind.value="name" on.input="name = this.value">
<!-- One-way databinding with no feedback;
on.input is needed to detect user activity -->
<input bind.value="name" on.input>
<!-- Usage in HTML -->
<hello-world></hello-world>
var Component = require('atril').Component;
Component({
tagName: 'hello-world'
})(ViewModel);
function ViewModel() {
this.name = 'world';
}
ViewModel.viewUrl = 'hello-world/hello-world.html';
<!-- Updates automatically -->
<h1>Hello, {{name}}!</h1>
<!-- Two-way databinding -->
<input twoway.value="name">
<!-- One-way databinding with manual feedback -->
<input bind.value="name" on.input="name = this.value">
<!-- One-way databinding with no feedback;
on.input is needed to detect user activity -->
<input bind.value="name" on.input>
Each custom element has an optional view, an HTML template. Given a component
class X
, the view can be provided in the following ways:
X.view => string
X.viewUrl => string
X.view => Promise => string
X.view => string
@Component({tagName: 'my-element'})
class X {
static view = `
<p>Hello {{name}}!</p>
<input twoway.value="name">
`;
}
This works well when importing views through the SystemJS text plugin:
import view from './my-element.html!text';
@Component({tagName: 'my-element'})
class X {
static view = view;
}
X.viewUrl => string
Primary method of view loading. The library automatically loads the view by the given URL, then activates the component once the view is available.
@Component({tagName: 'my-element'})
class X {
static viewUrl = 'my-element/my-element.html';
}
Loaded views are synchronously available through the viewCache
utility exposed
by the library. It can also load a hitherto unavailable view, or set a view by
the given URL without loading it over the network. For production, views should
be converted to JS and precached in the viewCache
. If you're building with
gulp
, use gulp-html-to-js
to
do this for you.
The build system for this documentation site includes view preprocessing; you're welcome to use it as an example. ([1])
X.view => Promise => string
You can return a promise that resolves to a view. Example using fetch:
let viewPromise = null;
@Component({tagName: 'my-element'})
class X {
static get view() {
return viewPromise || fetch('/my-secret-view-url')
.then(response => viewPromise = response.text());
}
}
This lets you load views asynchronously through custom means.
The library uses a variant of dependency injection — dependency assignment
— to give you contextual dependencies for each viewmodel. To get hold of them,
use the @assign
decorator (ES7/TypeScript) or the static assign
property
on the constructor function (ES5).
The component viewmodel has just one dependency: element
.
import {Component, assign} from 'atril';
@Component({tagName: 'my-element'})
class VM {
@assign element: Element;
constructor() {
console.log(this.element);
}
}
You can also use a different property name:
import {Component, assign} from 'atril';
@Component({tagName: 'my-element'})
class VM {
@assign('element') elem: Element;
constructor() {
console.log(this.elem);
}
}
var Component = require('atril').Component;
Component({tagName: 'my-element'})(function() {
function VM() {
console.log(this.elem);
}
VM.assign = {elem: 'element'};
return VM;
}());
A component's life begins with a constructor
call. In addition, it can define
two lifecycle methods: onPhase
and onDestroy
.
onPhase
This is called whenever the library reflows the tree of components and bindings in response to user activity. Example usage:
class VM {
@assign element: Element;
onPhase() {
let anchor = this.element.querySelector('a');
if (this.isActive(anchor, location.pathname)) {
anchor.classList.add('active');
} else {
anchor.classList.remove('active');
}
}
}
onDestroy
When a root of the component hierarchy is removed from the real DOM, this is called for each descendant component and the root itself. You can use this as a chance to free memory or perform other cleanup tasks. Example:
class VM {
constructor() {
createWastefulResource();
}
onDestroy() {
deleteWastefulResource();
}
}