Attribute mapping
Learn about binding values to attributes of DOM elements and how to extend the attribute mapping with great ease.
When dealing with Aurelia and custom elements, we tend to use the @bindable
decorator to define bindable properties. The bindable properties are members of the underlying view model class. However, there are cases where we want to work directly with attributes of the DOM elements.
For example, we want an <input>
element with a maxlength
attribute and map a view model property to the attribute. Let us assume that we have the following view model class:
Then, intuitively, we would write the following template:
This binds the value to the maxlength
attribute of the <input>
element. Consequently, the input.maxLength
is also bound to be 10
. Note that binding the value of the maxLength
attribute also sets the value of the maxLength
property of the input element. This happens because Aurelia, in the background, does the mapping for us.
On a broad level, this is what attribute mapping is about. This article provides further information about how it works and how to extend it.
How it works
To facilitate the attribute mapping, Aurelia uses IAttrMapper
, which has information about how to map an attribute to a property. While creating property binding instructions from binding commands, it is first checked if the attribute is a bindable. If it is a bindable property, the attribute name (in kebab-case) is converted to the camelCase property name. However, the attribute mapper is queried for the target property name when it is not a bindable. If the attribute mapper returns a property name, then the property binding instruction is created with that property name. Otherwise, the standard camelCase conversion is applied.
If we want to bind a non-standard <input>
attribute, such as fizz-buzz
, we can expect the input.fizzBuzz
property to be bound. This looks as follows.
Extending the attribute mapping
The attribute mapping can be extended by registering new mappings with the IAttrMapper
. The IAttrMapper
provides two methods for this purpose. The .useGlobalMapping
method registers mappings applicable for all elements, whereas the .useMapping
method registers mapping for individual elements.
To this end, we can grab the IAttrMapper
instance while bootstrapping the app and register the mappings (there is no restriction, however, on when or where those mappings are registered). An example might look as follows.
In the example above, we are registering a global mapping for foo-bar
attribute to FooBar
property, which will apply to all elements. We are also registering mappings for individual elements. Note that the key of the object is the nodeName
of the element; thus, for an element, it needs to be the element name in upper case. In the example above, we map the fizz-buzz
attribute differently for <input>
and <my-ce>
elements.
With this custom mapping registered, we can expect the following to work.
Use two-way binding for attribute
In addition to registering custom mappings, we can teach the attribute mapper when using two-way binding for an attribute. To this end, we can use the .useTwoWay
method of the IAttrMapper
. The .useTwoWay
method accepts a predicate function determining whether the attribute should be bound in two-way mode. The predicate function receives the attribute name and the element name as parameters. If the predicate function returns true
, then the attribute is bound in two-way mode, otherwise it is bound in to-view mode.
An example looks as follows.
In this example, we are instructing the attribute mapper to use two-way binding for fizz-buzz
attribute of <my-ce>
element. This means that the following will work.
Live example
A similar example can be seen in action below.
Last updated