Best Parts of Aurelia Part 1 – Composing custom elements / templates

In a renewal of a previous set of posts that I did around Durandal.js, I wanted to start a new series on Aurelia, the successor to Durandal.js and Caliburn.Micro.  Aurelia is an awesome collection of libraries that make up an amazing framework that is focused on future specs of the JavaScript language while still supporting a large set of current browsers.

Code

For this quick highlight of the features we will use this repo, which is a basic layout out with multiple columns which contain some widgets –

https://github.com/PWKad/aurelia-layout

It is a basic fork of the Aurelia skeleton-navigation. For any questions please visit the official gitter –

https://gitter.im/Aurelia/Discuss

Composing templates and custom elements

What’s the difference between using the compose element vs a custom elements? In what situations do we use each of them with Aurelia? How does it fit into the web components spec?

Compose:

  1. Can be used to dynamically compose templates (not forced to define the template type)
  2. Can use same view model but different view for each item in an array
  3. You can separate your views and view models into the smallest bits of (re-usable) code needed.

Imagine you want to create between 1 and 3 number of columns that are in charge of their own visibility and data-binding.  Easy.  Using the compose element with a repeat.for binding.

<compose view-model="./column" model.bind="column" repeat.for="column of selectedColumns.columns"></compose>

In this case we are using the compose element to bind to a template where we can provide our own view-model, view, or model. Let’s dissect what we did above –

Set the view-model to ./column to let the compose custom element know that A view-model can be found in this same folder “./” named column.js “column”. The compose custom element will then look for a corresponding view to bind to, and locates in the same folder or by convention if we have one set.

What’s really cool is we could have bound the view-model to a property so if we were iterating over an array of columns in our parent view-model we could have set a different view-model / view pair based on some condition, such as ‘columns/green’ to indicate a column in good standing vs ‘columns/red’ to represent one in bad standing.

We bind the model to the column, which in this context is set to a column we defined in our repeat.for binding.

This means in the parent view-model (such as layout.js) we need to define a property called columns which is an array, such as –


export class Welcome{
	constructor(){
		this.columns = [{ name: 'column1' }, { name: 'column2' }];
	}
}

Custom element:

A custom element is basically a web component. It uses the future spec to create a view-model / view pair that is highly re-usable and componentized.

Now we need to create a column view template in the same folder. Let’s create a new .html file and wrap the .html in a template tag that let’s Aurelia know this is a template that we want to re-use.



  <div class="column">${column.name} if you lower-case it is ${column.lcName}.</div>


Next we create a view-model to pair up to it to allow handling any of this particular columns’ properties or methods. In this example we create an ES6 module but in our skeleton-navigation app Aurelia can convert this to your favorite module format (AMD, CommonJs, etc…)


export class Column {
  constructor () {
    this.column = { name: '', lcName: '' };
  }

  activate(col) {
    this.column.name = col.name;
    this.column.lcName = col.name.toLowerCase();
  }
}

Here we are defining an activate callback that the module loader will call when first activating the view-model and pass the column we bound the model to before in the parent. This was the compose binding before where we used model.bind in our view.

How does it work together?

In the case above the view model column.js and view column.html make up a custom element. We used the compose custom element to instantiate an instance of it to dynamically render it in the DOM.

What else could we do instead?

We could import it directly in the DOM –

  <import from='./column'></import>

And then use it as a custom element there directly –

<column model.bind="column"></column>

The difference here is we are forced to use the column view-model and view pair.

Wrap-up

What we went over –

1. Compose’ing custom elements allow a very dynamic way to create partials that truly separate out our code in to functional areas.

2. Custom elements are web components that need to be imported or composed to use.

3. The templating engine for Aurelia is truly amazing and built for the future, but designed for use now.

Advertisements

Best parts about Durandal

I wanted to start a quick series dedicated to what makes Durandal so powerful to yield.

Part 1 – 

The Compose binding

With Durandal, you can dynamically load views and AMD modules directly from the DOM.  Why is this important?

  1. You can separate your views and modules into the smallest bits of (re-usable) code needed.
  2. Render your content more dynamically.
  3. You can easily instantiate and show either Singleton objects or Constructors.  (wait what?)

Separating your code

Imagine you want to create ‘n’ number of widgets that are in charge of their own visibility, data-fetching, and data-binding.  Easy.  Using the compose binding with a foreach, you can instantiate the modules in your view model and use the DOM to render them.

View –

<ul data-bind="foreach: widgets">
     <li data-bind="compose: widgetPath"></li>
</ul>

View Model –

define([], function () {
    var widgets = ko.observableArray();
    function initializeViewModel() {
        widgets.push(new Widget('Compose a widget module', 'viewmodels/exampleone'));
        widgets.push(new Widget('Compose a widget view only', 'exampletwo'));
    }
    function activate() {
        initializeViewModel();
    }
    function Widget(title, path) {
        var self = this;
        self.Title = title;
        self.widgetPath = path;
    }
});

What did we just do?

1. When we initiated the view model, we added two widgets into an observable array containing widgets.
2. The first widget is a path to a view model, which Durandal finds an associated view for and properly recognizes as part of a module.
3. The second widget is a path to a view, with no corresponding view model.

Whats interesting to note here is that in the first widget with a corresponding view model, Durandal helps us by creating a context around that view that is bound to the view model. This means if we reference something in the view model that Knockout and Durandal bind any related view elements to that instance of the view model. This is most ideal when not using singletons, as the every instance of a singleton will be bound to the same context.

In the second widget we are simply binding to the current context, which in this case would be the widget.  This is derived from the foreach binding that the widget is nested inside of, so you could easily bind to the title property or you could also reference the parent context using the Knockout $parent reference.

The reason $parent would not work in the first example is that the new context does not directly know who the parent is.  What if we wanted it to?  That’s easy as well, using activationData

Activation Data –

Using the activationData option of the compose binding, we can inject data or even context into the composed view model.  This is an excellent way of injecting dependency, and I could go on for hours on the various ways you can use this in your application, but let’s keep it simple and show an example.

Let’s bind our view up to widgets using Knockout’s containerless binding –

<!-- ko foreach: widgets -->
    <!-- ko compose: { model: widgetPath, activationData: { data: $data } } -->
    <!-- /ko -->
<!-- /ko -->

You’ll notice that I passing an object into the compose binding that contains a model and activationData, which contains an object of it’s own. You can read more on the Durandal website on the various parameters (http://durandaljs.com/documentation/Using-Composition.html) but basically the model is declaring that at that path, there is a view model to use for binding. Durandal automatically finds the corresponding view, and then injects our activationData into the view model. To intercept it, you only need to have a parameter in your activate method of the widget. Notice I am injecting $data, which basically just injects the current context into the view model. You could just as easily inject strings, observables, and even parent contexts.

Grabbing the data in our widget’s view model –

define([], function () {
    var thisContext = ko.observable();
    function activate(activationData) {
        // Set thisContext equal to the injected data
        thisContext(activationData.data);    
    }
    function Widget(title, path) {
        var self = this;
        self.Title = title;
        self.widgetPath = path;
    }
});

Now you can bind thisContext in the view however you want. A solid strategy is to use the with binding in your view that is set to thisContext, that way if for some reason your view model is instantiated without data it will just appear empty as opposed to breaking your bindings.

That’s it! Feel free to leave comments if you notice any problems or have any suggestions or questions. If you have any Durandal questions please direct them to StackOverflow.com or the Durandal.js Google Group, where the active community will help resolve them.