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 => stringX.viewUrl => stringX.view => Promise => stringX.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 => stringPrimary 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 => stringYou 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.
onPhaseThis 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');
}
}
}
onDestroyWhen 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();
}
}