Easily the most popular Aurelia-related question I see on StackOverflow is some variant of “how do I use my favorite CSS framework within Aurelia?” Most CSS frameworks today are built to be used from a $(document).ready
callback; but rich Aurelia applications have various routes with unique views that are created dynamically according to user input. We need a way to hook into Aurelia’s templating system and respond when individual elements are ready
.
The HTML5 standard has a number of built in tags for common elements, such as <input>
, <table>
, or <video>
. Custom elements are the Aurelia way of defining a new element tag that “just works”. Today, we’re going to build a custom element, <s-progress>
, that uses the progress module from Semantic-UI.
Creating a custom element
A custom element is just a simple view/view-model pair, similar to those we use to define routes. Like any other view/view-model pair, it has access to the full activation lifecycle, and this is how we will set-up and tear-down the CSS framework code.
sProgressCustomElement.js
import { inject, bindable, customElement, bindingMode } from 'aurelia-framework';
// this is how to import jquery and semantic-ui, which we will be using
// in our progress bar custom element
import $ from 'jquery';
import 'semantic-ui';
@inject(Element)
// this tells Aurelia what the tag name of the custom element will be;
// we can omit the decorator if our class name matches the convention
// TagNameCustomElement
@customElement('s-progress')
// while custom element class names are expected to be init capped, their
// respective custom element will be lower case hyphenated; our custom
// element below translates to the <s-progress> tag
export class SProgressCustomElement {
// we can define an optimal default binding mode for attributes that
// are designed to be bound only once (oneTime) or only from the
// viewModel (oneWay)
@bindable({ defaultBindingMode: bindingMode.oneTime }) label;
@bindable({ defaultBindingMode: bindingMode.oneWay }) labeled;
@bindable progress;
constructor(element) {
this.element = element;
}
// here we enable both aurelia binding and standard HTML attributes, so
// we can use both syntaxes, for example <s-progress labeled> or
// <s-progress labeled.bind="showLabel">
bind() {
// since we told aurelia that these properties are bindable, the binding
// system has already given values to these properties if available, so
// we pull from the html attribute if those values are not set
this.labeled = this.labeled || this.element.hasAttribute('labeled');
this.label = this.label || this.element.getAttribute('label');
this.progress = this.progress || this.element.getAttribute('progress');
}
// this lifecycle callback is called when the element is in the DOM
// and ready for manipulation, and so this is where we call all the
// semantic-ui code on the element
attached() {
$(this.element).progress({
text: {
active: this.label
}
});
}
// AFAIK, semantic-ui doesn't have / need teardown functions; however,
// some frameworks do, and this is where you would call them
detached() { }
// by convention, we can listen and respond to changes in any bindable
// property by creating a callback function called {attribute}Changed,
// and we can use this function to call the semantic-ui api
progressChanged(newValue) {
$(this.element).progress('set progress', newValue);
}
}
sProgressCustomElement.html
<template class="ui progress">
<div class="bar">
<!-- we use show here, because we want this element present at all
times so semantic can access it, even when it is hidden -->
<div show.bind="labeled" class="progress"></div>
</div>
<!-- here too -->
<div show.bind="label" class="label">${label}</div>
</template>
Using a custom element
Next, we use the custom element in our application, just like we use any other element tag. Since we enabled both standard HTML and aurelia-flavored binding, we can use either syntax, as needed. We can also override the default binding behavior, if necessary.
app.html
<template>
<require from="sProgressCustomElement"></require>
<s-progress class="indicating" progress.bind="progress" label="{percent}% awesome" labeled></s-progress>
<s-progress class="inverted" progress.bind="progress" labeled.one-way="showLabel"></s-progress>
</template>
Notes
This is a very brief example of how to create a custom element wrapping a css framework. Even this example doesn’t completely cover all the features of the progress bar. My goal in this article is to introduce any beginning-to-intermediate developer to the concepts and some of the skills required to wrap a css framework. After writing this article, I’ve considered the possibility of beginning an official Aurelia-flavored Semantic-UI library; however, this would be a large undertaking. I would be honored if I could collaborate with some of my readers on this project. Please shoot me an email if this is something that might interest you.
When I started writing the example project for this article, I began using the esnext-webpack skeleton. I bailed in favor of the jspm-enabled esnext skeleton, as there were a lot of issues with getting semantic-ui to work in a webpack environment. The first reason for this is because the semantic-ui package.json
does not expose a main entry point. You can overcome this issue by using import 'semantic-ui/dist/semantic.min'
. The next reason is the way that semantic-ui was looking for the jQuery global, which wasn’t supported in Easy Webpack. I opened an issue leading to this fix, but by this time I had already completed the article, and haven’t had a chance to test it. Hopefully this information is useful to my pro-webpack readers.
Links
Demo Page
Progress Bar at Semantic-UI
Custom Elements at Aurelia HUB
Databinding cheat sheet
EasyWebpack global jQuery config
Semantic-ui with Webpack
jQuery dependency with Webpack