Originally published at: http://www.sitepoint.com/extending-html-aurelia-io-way/
Teaching the old dog HTML new tricks is a major focus of today’s modern JavaScript frameworks. Be it by following potential standards like WebComponents, creating custom Directives or by extending existing classes, chances are high that your framework of choice provides a means to extend HTML’s markup itself. In a previous article, written by Brad Barrow, you were introduced to a new player: Aurelia. This article will build upon Brad's article and code and show you how to create custom elements as well as custom attributes by following Aurelia’s conventions.
The complete code for this article can be found on our GitHub repo and you can see a demo of what we are going to build here (please allow some time for the app to initialize).
Why the Need for More Markup?
Before jumping straight into the action let’s first understand the potential use case for creating new components. To do so we will take a conceptual look at the introductory example as shown in the figure below. We have two pages, represented by a ViewModel
(VM) and a View
, showing funny pictures and gif videos. Each of those has a repeated list which itself renders posts containing an image and a text block.
Aurelia Reddit Client conceptual diagram
By looking at the View we can see that data acquisition, as well as rendering, is tightly coupled in one VM/View pair.
<template> <ul class="list-group"> <li class="list-group-item" repeat.for="p of posts"> <img src.bind="p.data.thumbnail" /> <a href="http://reddit.com${p.data.permalink}"> ${p.data.title} </a> </li> </ul> </template>
This might not be a problem with a simple example, but can turn into a major drawback as the system grows and more and more requirements are gathered.
Enhancing Existing Elements with Custom Attributes
Imagine we get a request to provide a popover for each of the funny pages posts. In order to do that we could easily wire up Bootstrap’s feature directly into the markup by placing the necessary data-
attributes followed by an initialization inside our FunnyVM
. But what if we suddenly need to do it on the other page as well? Providing the feature by declaring a custom attribute can make our lives much easier. These are particularly useful in following scenarios:
- Wrapping existing plugins
- Shortcuts for common bindings such as style or class
- Altering existing HTML elements / custom elements without direct code access
Now let’s get our hands dirty and see what it takes to build our first custom attribute.
Creating the popover
Let’s start by looking at what we’d like to achieve. The new attribute popover
should accept parameters for the placement
, title
and content
of the popover. The placement is fixed to the right, so a simple string as value is enough. For the other two properties we’re going to use Aurelia’s data binding to map the iterated values. In order to load the file we make use of Aurelia’s require
feature. The from
attribute contains the relative path to the resource to be imported.
<require from="./popover"></require> ... <img src.bind="p.data.thumbnail" popover="placement: 'right'; title.bind: p.data.url; content.bind: p.data.title" />
In order to make that happen, we start by creating a new JavaScript file in the src
folder called popover.js
. The custom attribute, like all other Aurelia constructs, is a simple exported ES6 class rather than a collection of functions passed into a predefined API (as many legacy frameworks do).
import {customAttribute, bindable, inject} from 'aurelia-framework'; import $ from 'bootstrap'; import bootstrap from 'bootstrap'; ...
Compared to other frameworks, Aurelia declares constructs by describing them via metadata
. But instead of using static functions or complicated APIs, Aurelia leverages cutting edge ES7 Decorators to achieve that. We’re going to import the necessary decorators from the package aurelia-framework
. As for the control itself, we will use the Popover JavaScript control provided by Twitter Bootstrap. So we import the jQuery handle $
as well as bootstrap
in order to initialize Bootstraps’ JavaScript code.
The next step is to apply the previously mentioned metadata so that Aurelia knows what it gets when it loads the file. By attaching the customAttribute
decorator we name our component with the given value. The bindable
decorator on the other hand is declaring a property which our View can bind to. We simply repeat this decorator for each available property.
@inject(Element) @customAttribute('popover') @bindable('title') @bindable('content') @bindable('placement') export class Popover { ...