Aurelia

Aurelia Custom Elements & Content Selectors

Introduction

Aurelia

Aurelia


Aurelia Custom Elements & Content Selectors

Posted by Aurelia on .
Featured

Aurelia Custom Elements & Content Selectors

Posted by Aurelia on .

This week, Core Aurelia Team member, Patrick Walters, shows us how to create custom elements that leverage content selectors.

Warning: This blog post covers a deprecated version of Shadow DOM, known as Shadow DOM v0. Aurelia 1.0 and beyond uses the current specification, known as Shadow DOM v1, which leverages slots. Please read our official documentation for more information.

About the Author

Patrick is a developer living in the DFW area and is passionate about making the developer experience as seamless and easy as possible in Aurelia, based on leveraging ideas from studying many other languages and frameworks. His preferred stack is Aurelia with Ruby on Rails and his preferred development environment is Sublime Text 3 with iTerm2 and Chrome.

Code samples: aurelia-modal on github

The Problem

Imagine that you want to have a modal dialog in your application but you don't want to have to include a ton of HTML every time you use it. You also might want to toggle the visibility of different sections of the modal, depending on its use. Perhaps you also want to be able to skin it differently based on its content.

The Solution

With Aurelia we can create custom elements that take advantage of Content Selectors to solve these problems. Aurelia uses concepts from the Shadow DOM to separate content and presentation. To see how this works we'll build a single, shared modal dialog that dynamically changes its content as needed. This will give us a single visibility property to manage. We will use Aurelia's compose and custom elements to swap out the content. Finally, we'll leverage content selectors to allow re-skinning the modal more easily and further abstracting style-specific logic out of our view, making it is easier to adjust styling.

Starting Out

As always let's get an Aurelia app running. To begin, download the navigation skeleton and unzip it. After that, we need to do some basic setup at the root of that project directory.

$ npm install
$ jspm install -y

$ gulp watch

Now we have our app being served at localhost:9000.

Using our Modal Dialog

In our src/app.html file, let's add a basic usage of the modal dialog that we want to create:

<modal>  
  <modal-header title="Edit Person"></modal-header>
  <modal-body content="person-information"></modal-body>
  <modal-footer buttons.bind="['Cancel']"></modal-footer>
</modal>  

Here we are using a modal custom element and and setting the header, body, and footer of it. So far we haven't actually created these custom elements, but I wanted to show you first how they would be used, so you can understand the purpose of the following steps which describe their creation.

Creating the Modal Element

Let's create the modal element first. There are two parts for each of the elements we will create, the View (HTML) and the View-Model (JS). Let's start with the view.

View - modal.html

<template>  
  <div class="modal fade" ref="modal">
    <div class="modal-dialog">
      <div class="modal-content">
        <content select="modal-header"></content>
        <content select="modal-body"></content>
        <content select="modal-footer"></content>
      </div>
    </div>
  </div>
</template>  

Here we are using Bootstrap's default modal implementation. Did you notice the content selectors in the modal-content div? That is how we tell our modal element where to render the different content types that we defined in the app.html. The value of the select attribute is actually a CSS Selector. Cool isn't it?

Here's the View-Model that encapsulates the behavior of the modal:

View-Model - modal.js

import $ from 'jquery';

export class Modal {  
  attached(){
    $(this.modal).modal();
  }
}

You can see in our attached callback that all we are doing is passing our referenced modal to the Bootstrap modal plugin.

Creating The Header and Footer

Now, let's create the header and footer elements. We'll start with the header's view:

View - modal-header.html

<template>  
  <div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
      <span aria-hidden="true">&times;</span>
    </button>
    <h4 class="modal-title">${title}</h4>
  </div>
</template>  

Again, this is just a base Bootstrap modal header. The only adjustment we've made is to add a string interpolation binding to render the title into the h4 element. Here's how we define the accompanying view-model:

View-Model - modal-header.js

import {bindable} from 'aurelia-framework';

export class ModalHeader {  
  @bindable title = '';
}

We create the bindable property title with a default value of '' (empty string). Also, note that we are using ES7 property initializers to create the property. If we didn't want to (or couldn't) use ES7 property initializers, we could always write that same code using class decorators, like this:

import {bindable} from 'aurelia-framework';

@bindable({name:'title', defaultValue:''})
export class ModalHeader{}  

And, just for the sake of completeness, if you didn't want to use any ES7 features, you could use pure ES6 like this:

import {Decorators} from 'aurelia-framework';

export class ModalHeader{  
  static decorators(){ 
    return Decorators.bindable({name:'title', defaultValue:''});
  }
}

Remember though, in all cases, this creates a custom element with a single HTML bindable property "title" that has a default value of the empty string.

The footer is constructed in a similar way. Let's see the view first:

View - modal-footer.html

<template>  
  <div class="modal-footer">
    <button type="button" class="btn btn-default" repeat.for="button of buttons">${button}</button>
  </div>
</template>  

Again, it's just the base bootstrap footer, but we've changed the button so we can display more, based on what is bound.

View-model - modal-footer.js

import {bindable} from 'aurelia-framework';

export class ModalFooter {  
  @bindable buttons = [];
}

Again we create a buttons property that is bindable and is an array which will contain the labels for our buttons.

Creating the Body

Now we need our modal-body template. It will be in charge of what gets shown in the main content area.

View - modal-body.html

<template>  
  <div class="modal-body">
    <compose view-model.bind="content"></compose>
  </div>
</template>  

It is the base bootstrap body, but we've added Aurelia's compose element with a binding so that it renders whatever gets set for the content property. Here's the view-model:

View-model - modal-body.js

import {bindable} from 'aurelia-framework';

export class ModalBody {  
  @bindable content;
}

Our content will be a string property which tells the view what to compose into the body.

Content to Render

We need to have some content to dynamically compose into the body of our modal. Let's just steal from the starter kit's welcome.html in order to keep things simple:

View - person-information.html

<template>  
  <form role="form">
    <div class="form-group">
      <label for="fn">First Name</label>
      <input type="text" value.bind="person.firstName" class="form-control" id="fn" placeholder="first name">
    </div>
    <div class="form-group">
      <label for="ln">Last Name</label>
      <input type="text" value.bind="person.lastName" class="form-control" id="ln" placeholder="last name">
    </div>
  </form>
</template>  

View-Model - person-information.js

export class PersonInformation {  
  constructor() {
    this.person = new Person();
  }
}

class Person{  
  firstName = 'Patrick';
  lastName = 'Patrick';
}

Here we just create a person class and instantiate it to be our 'person'. This will provide some demo data for our person-information.html

Summary

Now, we have all the pieces for a functioning modal dialog which can dynamically display different content! To use it, don't forget to require the elements in your view or register them globally at startup (this is what my plugin does). As a reminder, here's the html you can now write to build a modal:

<modal>  
  <modal-header title="Edit Person"></modal-header>
  <modal-body content="person-information"></modal-body>
  <modal-footer buttons.bind="['Cancel']"></modal-footer>
</modal>  

For the title we just used a string. The modal-body has a content property that tells it which template to render as the body. You can see this in our modal-body.html where we used the compose binding to render some content. Finally, buttons is an array of button labels to show in our modal.

We've separated our template logic that is specific to bootstrap out of our main app. Now if we wanted to we could switch from Bootstrap to Semantic-ui or some other CSS framework and swap it in and out as needed. That's it!

View Comments...