I know, I just can’t keep quiet about Aurelia. But it is one of the most exciting things to happen to Javascript in a long time. So I decided to write up a quick tutorial on creating a custom element in Aurelia for Select 2, which is a popular library for adding custom select elements to your applications.
Prep
Before we begin, you need to have Aurelia installed locally and the development server running. The easiest way to do this is to download the Aurelia Skeleton Navigation project and follow the README instructions. This tutorial assumes you already have an Aurelia project and that you’re not starting from scratch.
Once you have the Skeleton Navigation project downloaded (or your local project) and all dependencies installed, we need to download Select 2. This can be achieved by opening up a terminal window in your project directory and typing jspm install select2
after the download is finished, the needed changes will be made to the config.js and package.json files.
Now we need to make sure we have jQuery installed as well. This can be achieved by typing in your terminal/PowerShell window: jspm install jquery
– okay, now we have everything that we need to continue.
Creating our custom element
In the src
directory we want to create a folder called resources
and in it create two files: custom-select.js
and custom-select.html
(One is our ViewModel and the other is the View).
In custom-select.js
Let’s focus on the Javascript part of our custom select first. Open up the file custom-select.js
and add in the following:
// Aurelia Framework specific functionality
import {bindable, inject, customElement} from 'aurelia-framework';
// Import JSPM modules we installed earlier
import $ from 'jquery';
import 'select2';
import 'select2/css/select2.css!'
@customElement('select2') // Define the name of our custom element
@inject(Element) // Inject the instance of this element
export class CustomSelect {
@bindable name = null; // The name of our custom select
@bindable selected = false; // The default selected value
@bindable options = {}; // The label/option values
constructor(element) {
this.element = element;
}
// Once the Custom Element has its DOM instantiated and ready for binding
// to happenings within the DOM itself
attached() {
$(this.element).find('select')
.select2()
.on('change', (event) => {
let changeEvent;
if (window.CustomEvent) {
changeEvent = new CustomEvent('change', {
detail: {
value: event.target.value
},
bubbles: true
});
} else {
changeEvent = document.createEvent('CustomEvent');
changeEvent.initCustomEvent('change', true, true, {
detail: {
value: event.target.value
}
});
}
this.element.dispatchEvent(changeEvent);
});
}
}
We are creating our custom element using a decorator called @customElement()
then specifying the tagname. This decorates our class with the require metadata Aurelia needs to know this is our custom element and what the element will look like when referenced in the DOM.
We are also using @bindable
which allows us to specify properties of our custom element which are bindable. We are using the default use of @bindable
but if you read the documentation, you can go deeper and customise many aspects of how a value is bound (one-way, one-time, two-way), callback functions and more.
More notably you can see we are triggering an event on the element itself (at the bottom of the Javascript file above). When I first started with Aurelia I was passing callback functions, with a parameter called callback
however, I encountered far too many issues which was a sign that it was not the right way to go about it.
I have been using React.js since it practically debuted and I became used to the concept of passing callback functions. Triggering events on the element is cleaner and allows us to specify a custom event listener on the element itself when we use it.
In custom-select.html
Now we have our ViewModel above defined, lets create the View. This is the easiest part of our custom element. All we are doing is wrapping a select element in the mandatory <template>
tags and adding in some binding values to bind to values passed to the custom select element.
<template>
<select name.bind="name" value.bind="selected" id.bind="name">
<option repeat.for="option of options" value.bind="option.value">${option.label}</option>
</select>
</template>
There isn’t really much to explain here. The <template>
tags are a requirement. And anything within is our custom element. We then bind to values specified as @bindable
on the custom element itself. These are the values that will be passed through from whereever our custom element is being used.
Using our Custom Element
Now that we have our new custom element. We can either globalise it using aurelia.globalResources()
(for another post) or we can require it in our required view. For the sake of simplicity, we are going to focus on manually requiring it in our view and using it.
Now to use our newfound custom element, lets assume we have a ViewModel called test-select.js
and an accompanying View called test-select.html
we will then provide some options for the custom element, a default selected value and a callback for the change event triggered.
test-select.js
export class TestSelect {
activate() {
this.selectOptions = [
{label: 'My Option', value: 'my-value'},
{label: 'Some Value', value: '1212'},
{label: 'Select Me!', value: 'fsdf32423_312'},
];
this.defaultSelected = this.selectOptions[0];
}
changeCallback(evt) {
// The selected value will be printed out to the browser console
console.log(evt.detail.value);
}
}
test-select.html
<template>
<require from="resources/custom-select"></require>
<select2 name="myCustomSelect" selected.bind="defaultSelected" options.bind="selectOptions" change.delegate="changeCallback($event)"></select2>
</template>
Conclusion
As you can see, custom elements are nothing more than standard ES2015 classes with a @customElement
decorator annotating the class. We can then inject the element instance which we can manipulate, using jQuery we find the input element and then apply the Select2 plugin.
Much like our standard ViewModel’s in Aurelia we might be using elsewhere, we have a view (unless we specify otherwise) which has the same name (unless otherwise specified). This is the beauty of Aurelia, everything is class based and through the use of decorators we can tell Aurelia what said piece of code does within our application.
Hopefully this helps you create some custom elements of your own. I realise I didn’t delve too deep in the specifics of everything we have learned here. Fortunately, the documentation explains everything above and more, so I highly advise you make yourself a tea or coffee and read about custom elements here.
If something doesn’t work, I have made an error you want to ask a question: please drop a comment below and I respond pretty quickly.
Thank you so much for this article, it helped me in my learning of Aurelia.
When I implemented your example I kept getting evt.detail.value = undefined. I put a breakpoint on the custom-select.js and found that the “event” didn’t have a “val” property, so i started going through the object properties. I found it on event.delegateTarget.value, I’m not sure if i did something wrong but wanted to post this in case i did and if you didn’t mind shedding some light on this. Thank you again.
Hello, i have same problem,
evt.detail.value = undefined
How to get value ?
I also want a value of this
Hi,
Thanks for the great article!
BTW, custom-select.html should probably look like this:
${option.label}
Hi Dwayne,
This is indeed a wonderful article, but unfortunately there seem to be several issues which I’m unable to solve. I am quite new to Aurelia and probably that’s the reason…
One of them is as stated by others, the fact that the new value of the select is not being passed through to the custom change event.
The other is that the two-way binding for the value attribute is not working. It stays the same as it was in the beginning.
When I don’t change the select into select2 in the attached handler, the two-way binding does work. However, once converted to select2, the selected value is not being updated.
Any ideas?
Thanks.
Hi, First of all a really great article on custom elements. I’ve tried above steps in my own project , but I’m facing a weird issue. In my case, “options” is not getting binded to my select control and therefore, nothing is getting displayed on the dropdown. Could you please help here?
I’ll update this article with the proper and working way of using Select2 with Aurelia.
@bindable options = {}; should be @bindable options = [];
Angel said it best:
“The other is that the two-way binding for the value attribute is not working. It stays the same as it was in the beginning.
When I don’t change the select into select2 in the attached handler, the two-way binding does work. However, once converted to select2, the selected value is not being updated.”
Got any fix for that? I realize that the article is like… super old.. but i bet it’s still quite often visited.
Check out this Gist, I used it to get through some issues with the same select2 binding/updating issue:
https://gist.github.com/mujimu/c2da3ecb61f832bac9e0
If you are using Select2 version 4.x.x then you need to do this:
https://github.com/aurelia/documentation/issues/202
could you implement icheck plugin with aurelia
Holy crap! This is awesome! Thank you, Dwayne!