I have started exploring Aurelia and figured building a treeview would be a good way to learn more about the framework. The following post is a write up of how I built my treeview.

There seems to be a community forming behind Aurelia, so I figured I'd give it a try and build something of manageable complexity. I landed on a treeview component since it requires me to touch upon several of the different binding types in addition to recursive templates. The treeview supports recursive rendering of a few U.S. states with cities and boroughs. There is also support for expand/collapse of nodes with children.

Aurelia is a modern framework, with support for ES6 out of the box, which requires some setup to get transpiling and the rest of the development environment up and running. In order to simplify the process I recommend downloading the provided skeleton project and use it as a baseline. Aurelia is still a new framework, but the documentation on their website is already impressive and answered most of my questions.

I am no stranger to web frameworks, but the skeleton project is a huge help when learning about Aurelia since it covers several typical SPA scenarios. Also, I was very impressed with the simple and intuitive data binding model offered by the framework.

For the purposes of my treeview I decided to model it as two custom templates; tree-view and tree-node. The components are bound to a decoupled view model which promotes reuse and simplifies unit testing. I am big fan of keeping my view models free of framework dependencies since it makes my code much more portable. This is important since we live in a time where new frameworks are popping up all over the place :-)

Anyway, here's the code:

JavaScript

node-model.js

export class NodeModel{ constructor(name, children){ this.name = name; this.children = children || []; this.visible = true; if(this.hasChildren()){ this.icon = 'fa fa-minus'; this.expanded = true; } } hasChildren(){ return this.children.length > 0; } toggleNode(){ for(var i = 0; i < this.children.length; i++){ this.children[i].visible = !this.children[i].visible; } this.expanded = !this.expanded; if(this.expanded === true){ this.icon = 'fa fa-minus'; } else{ this.icon = 'fa fa-plus'; } } }

tree-view.js

import {Behavior} from 'aurelia-framework'; import {NodeModel} from 'node-model'; export class TreeView { constructor(){ var texas = new NodeModel('Texas',[new NodeModel('Houston'), new NodeModel('Austin')]); var newYork = new NodeModel('New York',[ new NodeModel('New York City', [new NodeModel('Manhattan'), new NodeModel('Brooklyn'), new NodeModel('Bronx'), new NodeModel('Queens'), new NodeModel('Staten Island')]), new NodeModel('Buffalo')]); var oregon = new NodeModel('Oregon',[new NodeModel('Portland')]); var california = new NodeModel('California',[new NodeModel('Los Angeles'), new NodeModel('San Francisco')]); this.nodes = [texas,newYork,oregon,california]; } }

tree-node.js

import {Behavior} from 'aurelia-framework'; import {bindable} from 'aurelia-framework'; export class TreeNode { @bindable current = null; }

Html

tree-view.html

<template> <require from='./tree-node'></require> <tree-node repeat.for="node of nodes" current.bind="node"></tree-node> </template>

tree-node.html

<template> <ul show.bind="current.visible"> <li> <div> <span if.bind="current.hasChildren()" click.trigger="current.toggleNode()" class="${current.icon}"></span> <span>${current.name}</span> </div> <tree-node repeat.for="node of current.children" current.bind="node"></tree-node> </li> </ul> </template>

main-page.html

<require from="./tree-view"></require> <tree-view></tree-view>

As you can tell, the code is relatively simple. At a high level it boils down to two templates with a shared recursive object model. The tree-node component is recursive since it calls itself as part of the template. Below is a screenshot of an unstyled version of the treeview: