UNPKG

@danielkalen/simplybind

Version:

Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.

304 lines (244 loc) 12.7 kB
--- { "name": "Templating: Custom Attributes", "culture": "en-US", "description": "An overview of the Aurelia templating engine's custom attribute functionality. Custom Attributes are used to add custom behaviors to DOM elements.", "engines" : { "aurelia-doc" : "^1.0.0" }, "author": { "name": "Ashley Grant", "url": "http://www.ashleygrant.com" }, "contributors": [], "translators": [], "keywords": ["JavaScript", "Templating", "Custom Attributes"] } --- ## [Introduction](aurelia-doc://section/1/version/1.0.0) Custom attributes are a category of view resource, just like value converters, binding behaviors and custom elements. Custom attributes provide the ability to alter the behavior or add functionality to any DOM element. Custom attributes may work by simply being added to a DOM element, or they may require that a value be set for the attribute. The value may be a simple value, or a complex set of options. These options may or may not be known when a custom attribute is developed. Aurelia provides simple APIs to create custom attributes of all of these types, while the Aurelia templating engine provides strong databinding capabilities for custom attributes. Many custom attributes will work directly on the DOM element they are attached to. It is not necessary to search the DOM for the element a custom attribute is attached to. This is accomplished simply by requesting the element be injected into the attribute by Aurelia's Dependency Injection system. Simply request for an object of type `Element` to be injected in to your attribute. Aurelia will ensure you are provided with the DOM element the attribute has been attached to. ## [Simple Custom Attribute](aurelia-doc://section/2/version/1.0.0) The simplest custom attribute to create is one that changes the behavior of an element merely by being added to the element. The following attribute will make an HTML element be displayed as a 100 pixel square with a red background. <code-listing heading="red-square${context.language.fileExtension}"> <source-code lang="ES 2015/2016"> export class RedSquareCustomAttribute { static inject = [Element]; constructor(element){ this.element = element; this.element.style.width = this.element.style.height = '100px'; this.element.style.backgroundColor = 'red'; } } </source-code> <source-code lang="TypeScript"> import {autoinject} from 'aurelia-framework'; @autoinject export class RedSquareCustomAttribute { constructor(private element: Element){ this.element.style.width = this.element.style.height = '100px'; this.element.style.backgroundColor = 'red'; } } </source-code> </code-listing> <code-listing heading="simple-attribute-usage.html"> <source-code lang="HTML"> <template> <require from="./red-square"></require> <div red-square></div> </template> </source-code> </code-listing> The attribute name is inferred via an Aurelia convention. This convention is `\${Name}CustomAttribute` for the ECMAScript class name. The class name is in "init caps" format. Aurelia converts this to "dash-case" format for use in HTML. Thus, `RedSquareCustomAttribute` becomes `red-square`. ## [Explicit Attribute Naming](aurelia-doc://section/3/version/1.0.0) Overriding the default naming convention is possible by utilizing the `@customAttribute` decorator. Provide this decorator with the exact name for your attribute as below. Note that Aurelia does not convert from "init caps" to "dash-case" when using this decorator. It uses the exact value passed to the decorator. <code-listing heading="red-square${context.language.fileExtension}"> <source-code lang="ES 2015/ES 2016/TypeScript"> import {customAttribute} from 'aurelia-framework'; @customAttribute('red-square') export class MyClass { // implement custom element here } </source-code> </code-listing> ## [Single Value Binding](aurelia-doc://section/4/version/1.0.0) Aurelia custom attributes support three different types of binding: single value binding, options binding, and dynamic options binding. The simplest of the three binding types is single value binding. Aurelia will automatically set the `value` property on the attribute's view-model. Note that this property is not set until databinding is complete. This means the `value` property will not be set in the custom attribute's constructor or in its `created` lifecycle event. It is available in the `bind` and later lifecycle events. <code-listing heading="square${context.language.fileExtension}"> <source-code lang="ES 2015/2016"> export class SquareCustomAttribute { static inject = [Element]; constructor(element){ this.element = element; this.element.style.width = this.element.style.height = '100px'; } bind() { this.element.style.backgroundColor = this.value; } } </source-code> <source-code lang="TypeScript"> import {autoinject} from 'aurelia-framework'; @autoinject export class SquareCustomAttribute { constructor(private element: Element){ this.element.style.width = this.element.style.height = '100px'; } bind() { this.element.style.backgroundColor = this.value; } } </source-code> </code-listing> <code-listing heading="Simple Attribute with Value Usage"> <source-code lang="HTML"> <template> <require from="./square"></require> <div square="red"></div> <div square="${color}"></div> <div square.bind="color"></div> </template> </source-code> </code-listing> Note that in the above code sample, the color of the square will not be updated, even if the bound value is changed. This is because the attribute is not notified when the `value` property changes. Aurelia can notify a custom attribute when its value has changed, via the `valueChanged(newValue, oldValue)` callback function. The `valueChanged` callback function, if implemented on the view-model, will be called whenever the attribute's value changes. This function has two optional parameters, `newValue` and `oldValue`. These parameters will be set to the new value of the attribute and old value of the attribute, respectively. The `value` property is still set by Aurelia even if the `valueChanged` function is implemented. <code-listing heading="square${context.language.fileExtension}"> <source-code lang="ES 2015/2016"> export class SquareCustomAttribute { static inject = [Element]; constructor(element){ this.element = element; this.element.style.width = this.element.style.height = '100px'; } valueChanged(newValue, oldValue){ this.element.style.backgroundColor = newValue; } } </source-code> <source-code lang="TypeScript"> import {autoinject} from 'aurelia-framework'; @autoinject export class SquareCustomAttribute { constructor(private element: Element){ this.element.style.width = this.element.style.height = '100px'; } valueChanged(newValue: string, oldValue: string){ this.element.style.backgroundColor = newValue; } } </source-code> </code-listing> <code-listing heading="Simple Attribute with Value Usage"> <source-code lang="HTML"> <template> <require from="./square"></require> <div square="red"></div> <div square="${color}"></div> <div square.bind="color"></div> <input type="text" value.bind="color" /> </template> </source-code> </code-listing> When the user updates the value of the `color` property via the textbox, Aurelia will call the `valueChanged` method on the custom attribute to alert the custom attribute to the change. ## [Options Binding](aurelia-doc://section/5/version/1.0.0) Options binding provides a custom attribute the ability to have multiple bindable properties. Each bindable property must be specified using the `bindable` decorator. The attribute view-model may implement an optional `\${propertyName}Changed(newValue, oldValue)` callback function for each bindable property. When binding to these options, separate each option with a semicolon and supply a binding command or literal value as in the example below. It is important to note that bindable properties are converted to dash-case when used in the DOM, while the VM property they are bound to are kept with their original casing. <code-listing heading="square${context.language.fileExtension}"> <source-code lang="ES 2015/2016"> import {bindable, inject} from 'aurelia-framework'; @inject(Element) export class SquareCustomAttribute { @bindable sideLength; @bindable color; constructor(element){ this.element = element; } sideLengthChanged(newValue, oldValue){ this.element.style.width = this.element.style.height = `${newValue}px`; } colorChanged(newValue, oldValue){ this.element.style.backgroundColor = newValue; } } </source-code> <source-code lang="TypeScript"> import {bindable, autoinject} from 'aurelia-framework'; @autoinject export class RedSquareCustomAttribute { @bindable sideLength: string; @bindable color: string; constructor(private element: Element){ } sideLengthChanged(newValue:string, oldValue:string){ this.element.style.width = this.element.style.height = `${newValue}px`; } colorChanged(newValue:string, oldValue:string){ this.element.style.backgroundColor = newValue; } } </source-code> </code-listing> <code-listing heading="Single Value Binding Usage"> <source-code lang="HTML"> <template> <require from="./square"></require> <div square="color.bind: squareColor; side-length.bind: squareSize"></div> </template> </source-code> </code-listing> ## [Dynamic Options Binding](aurelia-doc://section/6/version/1.0.0) Utilizing dynamic options, a custom attribute may deal with bindable properties where the name of the property is not known when creating the attribute. Simply decorate the attribute's view-model with the `dynamicOptions` decorator and implement the `propertyChanged(name, newValue, oldValue)` callback function. Aurelia will provide the name of the option that has changed along with new and old values for the option. Binding to a dynamic options attribute works exactly the same as binding to an options attribute in the DOM. <code-listing heading="square${context.language.fileExtension}"> <source-code lang="ES 2015/2016"> import {dynamicOptions, inject} from 'aurelia-framework'; @dynamicOptions @inject(Element) export class SquareCustomAttribute { constructor(element){ this.element = element; } propertyChanged(name, newValue, oldValue){ switch(name){ case 'fill': this.element.style.backgroundColor = newValue; break; case 'size': this.element.style.width = this.element.style.height = `${newValue}px`; break; default: this.element.style[name] = value; break; } } } </source-code> <source-code lang="TypeScript"> import {dynamicOptions, autoinject} from 'aurelia-framework'; @dynamicOptions @autoinject export class RedSquareCustomAttribute { constructor(private element: Element){ } propertyChanged(name: string, newValue: string, oldValue: string){ switch(name){ case 'fill': this.element.style.backgroundColor = newValue; break; case 'size': this.element.style.width = this.element.style.height = `${newValue}px`; break; default: this.element.style[name] = value; break; } } } </source-code> </code-listing> <code-listing heading="Single Value Binding Usage"> <source-code lang="HTML"> <template> <require from="./square"></require> <div square="fill.bind: squareColor; size: 100"></div> </template> </source-code> </code-listing> ## [Globally Accessible Custom Attributes](aurelia-doc://section/7/version/1.0.0) In all of our examples, we've been using the `require` element to import custom attributes we need into our view. There's an easier way. If you have some commonly used custom attributes that you'd like to make globally available, use Aurelia's `globalResources` function to register them. This will will eliminate the need for `require` elements at the top of every view.