Using built-in behaviors

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.

show

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.

hide

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.

if

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.

Note

A template controller is an attribute that transforms the element it is on into a template. It can then control how this template is rendered. The standard attributes if and repeat are template controllers.

repeat.for

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.

Binding to a map

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.

Repeat n times

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.

Repeating templating

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.

Contextual variables

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 otherwise

The with attribute

The 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

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.

The compose element

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.

Note

The code snippets in the following sections are excerpts of samples/chapter-3/composition. While you read this section, you can run the sample application in parallel so you can view live examples of composition.

Rendering a view-model

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.

Passing activation data

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.

Rendering a template

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.145.193