Common advice I’ve come across when building apps using SPA frameworks is to avoid using jQuery and other direct DOM manipulation techniques in favor of letting the framework update the DOM as a result in changes in your models state. This is generally fantastic advice, and I try to follow it where possible in my own Aurelia projects. But, often situations arise where a third party library (even ghasp a jQuery plugin) already exists that perfectly fits the problem at hand. One of the problems that plugins like selectize and select2 solve well is enhancing the out of the box functionality of the
select element to provide a better user experience with auto-complete and more. In this post, we’ll look at how you can create a country picker component by combining the jQuery based selectize control, along with a country flags library to create a country picker component in Aurelia.
This article will cover the following points:
- Adding the selectize control into an Aurelia CLI project using NPm and requireJS
- Plugging the selectize control into the Aurelia component life-cycle using the
- Dynamically compiling Aurelia templates on the fly to render
By the end of this post, we’ll have created a working country-picker control that allows users to pick their country from an auto-complete list. To make this list a little more user-friendly we’ll render the corresponding country flag associated with a given country code. You can see the end result in the below screengrab:
You can see the custom element in action here.
Setting up the pre-requisites
To begin with we’ll need to install the three per-requisite packages via NPM: selectize, and flag-icon-css, and of course jquery:
With these installed, the next step is to modify the
aurelia.json file as follows to ensure that the packages are bundled correctly into your Aurelia
vendor-bundle.js file. In case you’ve not come across this yet, Aurelia CLI + requireJS projects create two bundles by default:
app-bundle.jsfile which includes the view-models, view-templates and so on that make up your Aurelia project
vendor-bundle.jsfile which includes the third party dependencies that your application relies on
You’ll notice that we’re modifying the
dependencies section to include the new plugins. The
flag-icon-css nodes include references to the css resources that should be bundled. This allows us to
require them into our Aurelia view templates just like any other kind of view-resource, as you’ll see in a moment. For brevity, I’ve only included an excerpt with the relevant part of the file here. If you’re interested you can see the complete file here.
With the bundling configured, most of our dependencies are now in place, but one more step remains. The flag icon files (used to render the country flag next to each country name) are referenced from the
flag-icon.css file using relative paths like this
url(../flags/4x3/ad.svg);, so we need to copy these files into that relative path from the root of the Aurelia application to have them load correctly. Fortunately, we can do this by modifying the build pipeline generated for us by the CLI. To do this we’ll create a new gulp task as shown below, and call it as a part of the
build step. This file relies on input and output paths defined in the
aurelia.json file above:
With this file in place, it can be included in the Aurelia build by modifying the
build.js file as shown below:
With the dependencies in place, we can now get to the more fun part of creating the component. We’ll structure this by first creating a generic
selectize component that takes an
http-query used to pull a list of results back as a JSON blob, and render the results as select items using an Aurelia view template.
Creating the ux-selectize component
We’ll create the
ux-selectize component as a custom attribute. One benefit of doing it this way rather than using a custom element is that the
select element can remain pretty-well unchanged. By applying the custom attribute to the
select element we can transform it into a selectize control, in a way that’s quite unobtrusive from perspective of the view template. Also, due to the way that selectize works, we’d need to know the custom element options up-front (passing them down from a parent view-model or using a static list), rather than fetching them dynamically as we are in this case, which rules out the option of using a custom element in this scenario.
There are a few interesting parts to note about this custom attribute. Firstly, we’re using the
attached Aurelia component life-cycle call-back to initialize the jQuery component. This is the preferred life-cycle hook to perform DOM interaction as it occurs when a component is attached to the DOM. It’s also worth noting that we’re using
unbind component life-cycle method to clean up the selectize control when the view is unbound.
dynamicOptions decorator is used to add new
bindable properties to the custom attribute at will without the need to declare them one-by-one. For performance reasons you may want to declare them individually if you have a well-known and controlled set of properties, you intend to bind against. Looking down further, we’ve Aureliarized the HTTP logic as well. Instead of using jQuery AJAX to fetch the country list from the remote server, we’re instead passing a function, which makes the call using the
aurelia-fetch-client module (as you’ll see shortly). Finally, we’re hooking into the
onChange selectize event and delegating it to the
select element. This will allow us to listen for this event from the
country-picker parent component and use the updated country value.
I’ll ask you to ignore the magic in the
getHtml method at the bottom of the file for now, and we’ll circle back to that later.
With this component in place, the next step is to create a
country-picker custom element to encapsulate the
selectize component, and provide the country-picking specific implementation details. This component imports the
selectize CSS files, and declares a new
select element decorated with the
ux-selectize custom attribute. The
selectize attribute binding expression takes 6 parameters:
- httpQuery : The method (declared on the
country-pickerview-model used to fetch the countries from the server)
- selected : The currently selected country
- templateUrl : The path to the country item template (relative path within the project)
- valueField : The field on the country model to use as the select option value
- searchField : The field on the country model to use as the lookup key
- labelField : The field on the country model used for the select option label
We also specify an initial option to be used when the select component is initialized. This includes the selected country name and code:
Creating the country-picker component
The next step is to create the corresponding
country-picker view-model. This defines the
getCountries method used to fetch countries from the
countries.json endpoint using the
aurelia-fetch-client module. This list is filtered by the country name that the user types into the input element. To improve efficiency filtering logic would be better implemented on the server side to minimize the payload we need to pull back from the server.
We’re also listening for the
countryChange event fired from the
ux-selectize view-model in order to set our currently selected country.
..and the magic
I promised that we’d circle back to how the
ux-selectize component renders its item templates, and now is the time. The selectize control has a
render method which you can pass HTML that should be rendered for each select item. A simple implementation would have just been to return a string literal representing the HTML we wished to return, which would have looked something like this:
/templates/country-item.html, then have Aurelia’s binding engine take care of data-binding the item and creating the output HTML. It turns out that this is doable, with the help of Aurelia’s
First, let’s take a look at the template that we’ll use to render each of the country select items. This is a basic Aurelia view template which uses string interpolation binding to render country details to the view. I’ll not go into this in too much detail:
So how is this template used by the
ux-selectize custom attribute? To begin with, we use the
viewEngine to create a view factory given the template URL
this.viewEngine.loadViewFactory(templateUrl). We do this up-front to avoid needing to repeat the process for every select item. Once we’ve got the view factory, we can use it to create a view instance
const view = factory.create(childContainer); and bind the current select item using the
view.bind(item);. To get the actual HTML of the view instance we need to attach it to a DOM element, which in our case is a wrapper div:
let country = DOM.createElement("div"),
view.appendNodesTo(country);. Finally, we can return the actual
outerHTML of the element to be rendered.
I’ve glossed over the details of how the view engine process works under the hood here, since the main purpose is to give you the gist of what is happening. If you’re interested in reading more, I’d recommend this great article by Jeremy Gonzalez which I used as a reference when figuring this stuff out.
Put all of this together, and we have a re-usable country picker component, built on top of the jquery based selectize utility.
Nice! But, what have we learned?
It’s not advisable to go wholesale DOM manipulation and jQuery in an Aurelia project, as you start to get away from the benefits that Aurelia’s powerful templating and binding system gives you. But, sometimes there is an existing tool that solves your problem perfectly. In cases where this tool exists as an NPM package (and let’s be honest what doesn’t these days), it’s a simple matter of installing it into your Aurelia project, and making the relevant configuration tweaks to your
We also saw how you can use the
dynamicOptions decorator to add bindable properties to your custom attributes at will, without the need to declare each property individually.
Finally, we saw that although Aurelia’s binding engine API can be daunting at first, it’s powerful when you get to understand a few of the building blocks. An example of one of these building blocks is the
viewEngine.loadViewFactory method, used for creating view instances given a template URL. This gives you the flexibility of creating your own patterns when Aurelia’s default templating and binding approaches don’t quite fit what you need to do.