The core library, aurelia-templating-resources
, provides a set of standard behaviors, built on top of aurelia-templating
, which can be used in any Aurelia template.
The show
attribute controls the visibility of the element, based on the value of the expression it is bound to:
<template> <p show.bind="hasError">An error occurred.</p> </template>
In this example, the p
element will be visible only when the model's hasError
property is truthy.
This attribute works by injecting a CSS class either in the document head or in the nearest ShadowDOM root, and by adding this CSS class on the element whenever it should be hidden. This CSS class simply sets the display
property to none
.
This is similar to show
, but with an inverted condition:
<template> <p hide.bind="isValid">Form is invalid.</p> </template>
In this example, the p
element will be hidden when the model's isValid
property is truthy.
Other than the inverted condition, this attribute works exactly like show
and uses the same CSS class.
The if
attribute is very similar to show
. The main difference is that, instead of simply hiding the element when the bound expression evaluates to a false
value, it completely removes the element from the DOM.
<template> <p if.bind="hasError">An error occurred.</p> </template>
With the if
attribute being a template controller, it is possible to put it directly on a nested template
element to control the visibility of multiple elements:
<template> <h1>Some title</h1> <template if.bind="hasError"> <i class="fa fa-exclamation-triangle"></i> An error occurred. </template> </template>
In this example, both the i
element and the text following it will be removed from the DOM when hasError
is false
Actually, when the condition is falsey, the element on which it is, won't be just removed from the DOM, its own behaviors and its children's will be unbound. This is a very important distinction, as it has major performance implications.
For the following example, let's imagine that some-component
is huge, displays lots of data, has many bindings, and is very memory and CPU consuming.
<template> <some-component if.bind="isVisible"></some-component> </template>
If we replace if
with show
here, the bindings for the whole component's hierarchy would still exist, consuming memory and CPU even when it is not visible. When using if
, the component is unbound when isVisible
becomes false
, reducing the number of active bindings in the application.
On the other hand, this means that, when the condition becomes truthy, the element and its descendants must be re-bound. In a scenario where the condition is often toggled on and off, it can be better to use show
or hide
. Choosing between if
and show
/hide
is mainly a matter of balancing priorities between performance and user experience, and should be backed with real performance tests.
The repeat
attribute, when used with the special for
binding command, can be used to repeat an element for a sequence of values:
<template> <ul> <li repeat.for="item of items">${item.title}</li> </ul> </template>
In this example, the li
element will be repeated and data-bound to each item in the items
array.
Instead of an array, a Set
object can also be data-bound too.
Being a template controller, repeat
actually transforms the element it is on into a template. This template is then rendered for each item in the bounded sequence. For each item, a child binding context is created, on which the item itself is made available using the name at the left of the of
keyword in the binding expression. This means two things: you can name the item variable however you want, and you can use it in the context of the item itself:
<template> <ul> <li repeat.for="person of people" class="${person.isImportant ? 'important' : ''}"> ${person.fullName} </li> </ul> </template>
In this example, a li
element will be inserted in the ul
element for each item in the people
array. For each li
element, a child context will be created, exposing the current item as a person
property, and an important
CSS class will be set on the li
if the corresponding isImportant
property of person
. Each li
element will contain the fullName
of its person
, as text.
Additionally, the children contexts created by repeat
inherit from the surrounding context, so any property available outside the li
element is available inside it:
<template> <ul> <li repeat.for="person of people" class="${person === selectedPerson ? 'active' : ''}"> ${person.fullName} </li> </ul> </template>
Here, the root binding context exposes two properties: a people
array and selectedPerson
. When each li
element is rendered, each child context has access to the current person
in addition to the parent context. That's how the li
element for selectedPerson
will have the active
CSS class.
The repeat
attribute uses one-way binding by default, which means that the bounded array will be observed, and any change made to it will be reflected on the view:
If an item is added to the array, the template will be rendered into an additional view and inserted at the appropriate position in the DOM.
If an item is removed from the array, the corresponding view element will be removed from the DOM.
The repeat
attribute is able to work with map
objects, using a slightly different syntax:
<template> <ul> <li repeat.for="[key, value] of map">${key}: ${value}</li> </ul> </template>
Here, the repeat
attribute will create, for each entry in the map
, a child context having key
and a value
properties, respectively matching the map
entry's key
and value
.
It is important to remember that this syntax works only for map
objects. In the previous example, if map
were anything else but a Map
instance, the key
and value
properties wouldn't be defined on the child binding context.
The repeat
attribute is also able to repeat a template a given number of times, using the standard syntax, when binding to a number value:
<template> <ul class="pager"> <li repeat.for="i of pageCount">${i + 1}</li> </ul> </template>
In this example, assuming the pageCount
is a number, the li
element will be repeated a number of times equal to pageCount
, with i
going from 0
to pageCount - 1
inclusively.
If what needs to be repeated is composed of multiple elements without a single container for each item, repeat
can be used on a template
element:
<template> <div> <template repeat.for="item of items"> <i class="icon"></i> <p>${item}</p> </template> </div> </template>
Here, the rendered DOM will be a div
element containing alternating i
and p
elements.
In addition to the current item itself, repeat
adds other variables to the child binding context:
$index
: The index of the item in the array$first
: true
if the item is the first in the array; false
otherwise$last
: true
if the item is the last in the array; false
otherwise$even
: true
if the item's index is an even number; false
otherwise$odd
: true
if the item's index is an odd number; false
otherwiseThe with
attribute creates a child binding context using the expression it is bound to. It can be used to re-scope part of a template, to prevent long access paths.
For example, the following template does not use with
, and person
is traversed multiple times when its properties are accessed:
<template> <div> <h1>${person.firstName} ${person.lastName}</h1> <h3>${person.company}</h3> </div> </template>
By re-scoping the top div
element to person
, the access to its properties can be simplified:
<template> <div with.bind="person"> <h1>${firstName} ${lastName}</h1> <h3>${company}</h3> </div> </template>
The preceding example is short, but you can imagine how a bigger template can benefit from this.
Additionally, since with
creates a child context, all variables available to the outer scope will be accessible inside the inner scope.
The focus
attribute can be used to data-bind an element's ownership of the document's focus to an expression. It uses two-way binding by default, which means that the variable it is bound to will be updated when the element gains or loses focus
.
The following code snippet is an excerpt of samples/chapter-3/binding-focus
:
<template> <input type="text" focus.bind="hasFocus"> </template>
In the previous example, the input
will get focused upon rendering if hasFocus
is true
. When hasFocus
changes to a false
value, the input
will lose focus
. Additionally, if the user gives focus
to the input
, hasFocus
will be set to true
. Similarly, if the user moves away from the input
, hasFocus
will be set to false
.
Composition is the action of instantiating a component and inserting it in a view. The aurelia-templating-resources
library exports a compose
element, allowing us to dynamically compose a component inside a view.
A component can be composed using the path of the JS file exporting its view-model:
<template> <compose view-model="some-component"></compose> </template>
Here, when rendered, the compose
element will load the some-component
view-model, instantiate it, locate its template, render the view, and insert it in the DOM.
Of course, the view-model
attribute can be bound to or use string interpolation:
<template> <compose view-model="widgets/${currentWidgetType}"></compose> </template>
In this example, the compose
element will display a component sitting inside the widgets
directory, based on the value of the currentWidgetType
property on the current binding context. Of course, this means that compose will swap the component when currentWidgetType
changes (unless a one-time binding is used).
Additionally, the view-model
attribute can be bound to an instance of a view-model:
src/some-component.js
import {AnotherComponent} from 'another-component'; export class SomeComponent { constructor() { this.anotherComponent = new AnotherComponent(); } }
Here a component imports and instantiates the view-model of another component. In its template, the compose
element can then be bound directly to the instance of AnotherComponent
:
src/some-component.html
<template> <compose view-model.bind="anotherComponent"></compose> </template>
Of course, this means that, if anotherComponent
is assigned a new value, the compose
element will react accordingly and replace the previous component's view with the new one.
When rendering a component, the composition engine will try to call an activate
callback method on the component, if it exists. Similar to the router's screen activating life cycle methods, this method can be implemented by components so they can act when they are rendered. It can also be used to inject activation data into the component.
The compose
element also supports a model
attribute. This attribute's value will be passed to the component's activate
callback method, if any.
Let's imagine the following component:
src/some-component.js
export class SomeComponent { activate(data) { this.activationData = data || 'none'; } } src/some-component.html <template> <p>Activation data: ${activationData}</p> </template>
When composed without any model
attribute, this component would display <p>Activation data: none</p>
. However, it would display <p>Activation data: Some parameter</p>
when composed like this:
<template> <compose view-model="some-component" model="Some parameter"></compose> </template>
Of course, model
can use string interpolation or can be data-bound too, so a complex object can be passed to the component's activate
method.
When used with a component that does not implement the activate
method, the model
attribute's value is simply ignored.
The compose
element can also simply render a template, using the current binding context:
<template> <compose view="some-template.html"></compose> </template>
Here, some-template.html
would be rendered into a view using the surrounding binding context. This means that any variable available around the compose
element would also be available to some-template.html
.
When used with the view-model
attribute, the view
attribute will override the component's default template. It can be useful to reuse a view-model's behaviors with a different template.
18.191.145.193