Custom HTML Element architecture involves building JavaScript applications entirely from, you guessed it, custom HTML elements.

Consider the following example:

<order-model get-list="{}" #orderdata="{value}" /> <bit-graph title="Analytics"> <bit-series data="{orderdata.totals}" /> </bit-graph>

This code demonstrates:

An element that can load data Composable widget elements (a graph with a line-series)

If our designer wanted to add another restaurant, all they would need to do is add another <order-model> and <bit-series> element.

Here's a working version of the same example in a JSBin.

JS Bin on jsbin.com

Just like HTML's natural advantages, composing entire applications from HTML building blocks allows for powerful and easy expression of dynamic behavior.

We've fallen in love with Custom HTML Element Architecture at Bitovi, and this article will explain why you should too, some examples of components we've created, and tips to make this architecture work well.

Some History

Before we dive into why its a good idea to write your entire applications by creating building blocks of custom HTML elements and assembling them, a bit of context...

Pre Web Components

Before custom HTML elements existed, to add a datepicker to your page, you would:

Load a datepicker script Add a placeholder HTML element: <div class='datepicker' /> Add JavaScript code to instantiate your datepicker: $('.datepicker').datepicker() Gather your stone tipped spears and forage for small animals to feed your family for the night.

Web Components

Web Components are a new way to define functionality in the browser: as custom HTML elements, with their own template and behavior.

With custom HTML elements, to add the same datepicker, you would:

Load a datepicker script Add the datepicker to your HTML or template: <datepicker value="{date}"/> Press the Filet Mignon button on your personal food replicator to celebrate your accomplishment.

If you're not familiar with Web Components, please read up on them before continuing with this article.

This may not, on the surface, seem like a major difference, but there are major advantages to developing this way, which we'll discuss.

Using Components in 2015

Right now, cross browser support for native (built in) web components isn't there. And even when it is, native web components will always be a bit underpowered compared to the versions that libraries can provide, with better support for memory cleanup, event handling, templating, data-binding, etc.

Almost every modern JavaScript framework lets you add a custom element either directly to the page or within a template.

In React (using JSX):

render: function() { return <div> <DatePicker value="date" /> </div>; }

In Angular:

<datepicker ng-model="date"/>

And in CanJS:

<datepicker value=“{date}"/>

Using can.Component

can.Component is CanJS' take on Web Components. It provides the ability to define custom HTML elements that contain:

a template

a viewModel object for storing state

event handlers

Using can.Component, we've been creating web applications that are completely componentised.

Why Custom HTML Element Architecture?

1. Ease of page composition

Why would you do this? Simply put, when everything is a custom HTML element, putting together dynamic and interactive pages is as simple as assembling HTML!

This is Tom, our UX Director.

He knows HTML really well, but JavaScript, not as much. But that's ok, because composing dynamic behavior with custom HTML elements is so easy, even Tom can do it!

Many people, even non-developers like Tom, are very familiar with HTML. In addition, it can easily express hierarchy, and the end result of any web application is HTML anyways.

When everything is built as a custom HTML element, you can easily express complex dynamic behavior with little to no JavaScript required.

2. Forced modularity

Back in 2010, we wrote Organizing a jQuery Application, which said:

The secret to building large apps is NEVER build large apps. Break up your applications into small pieces. Then, assemble those testable, bite-sized pieces into your big application.

Components allow that direction to be realized even more completely. Designing components forces you to think in terms of small, bite-sized building blocks.

Small, isolated components are easier to debug, test, and understand, and minimize the impact of a poor decision to just that localized part of your application.

3. Reuse

As web components start to hit the mainstream, they will create an ecosystem of open source, reusable building blocks. This has already begun. We've created our own repository of shared, reusable can.Components that can be used with any module loader, such as this graph component. There is even already a web components gallery site for publishing and finding web components.

On a smaller scale, as your organization creates customized components for your applications, if you design them right, they will enable reuse in future applications.

Tag Team!

Simply replacing your visual widgets with custom HTML elements (like your datepickers) has some convenient advantages. But the power of custom HTML element architecture becomes more apparent when data is also expressed with HTML elements.

This "tag team" of data elements and widget elements creates unique advantages when used together.

Back to our original code example:

<order-model get-list="{}" #orderdata="{value}" /> <bit-graph title="Analytics"> <bit-series data="{orderdata.totals}" /> </bit-graph>

This simple template combines a request for data with an element that expresses it. Its immediately obvious how you would add or remove features from this, allowing for quick changes and easy prototyping. Without this architecture, the same changes would require more difficult code changes and wiring those changes up with widget elements that display the data.

The ability to easily add data elements to the page is coming with the upcoming release of can-connect.

Widget Custom Element Examples

We recently built an application for a client that was entirely built around components - from its routing, to its pagination behavior, to its model layer, to visual aspects like form wizards.

The following are some of the components we created for our client's application.

1. bit-wizard

The bit-wizard component creates a multipart form. Using it requires writing form steps, as seen below:

<bit-wizard done="{wizardCompleted}" step="{wizardStep}"> <bit-wizard-step {{#if firstName}}skip{{/if}}> Set a firstName:<br> <input can-value="firstName"><br> <button>Next</button> </bit-wizard-step> <bit-wizard-step {{#and firstName lastName}}skip{{/and}}> Set a lastName:<br> <input can-value="lastName"><br> <button>Next</button> </bit-wizard-step> <bit-wizard-step>Three <button>Next</button></bit-wizard-step> <bit-wizard-step>Four <button>Next</button></bit-wizard-step> </bit-wizard> {{#if wizardCompleted}} Wizard is done! {{else}} Current step: {{wizardStep}} {{/if}}

done and step are internal properties of bit-wizard 's viewModel. wizardCompleted (a boolean) and wizardStep (a number) are variables in this template's, which are bound to done and step via HTML attributes. As the state of the component changes, wizardCompleted and wizardStep are changed live. Those variables are used to render a message when the wizard is done, at the bottom of the template.

Without requiring a single line of JavaScript, this template creates a multipart form wizard and express its state. Powerful!

2. gmaps-map

The gmaps-map component was created as a wrapper around the Google Maps API. It allows easy placement of Google maps without having to interact directly with its JavaScript API, and makes the map data driven.

Here's an example showing a template that renders a map.

<gt-geolocation watch accuracy="{currentAcc}" lat="{currentLat}" lng="{currentLng}"/> <user-request auto method="findAll" value="{users}" /> <gmaps-map autocenter lat="{currentLat}" lng="{currentLng}" zoom=17> <!— Show user their current location —> <gmaps-marker lat="{currentLat}" lng="{currentLng}"/> <gmaps-circle lat="{currentLat}" lng="{currentLng}" radius="{currentAcc}"/> <!— Populated as soon as the user-request ajax request completes —> {{#each users}} <gmaps-marker lat="{lat}" lng="{lng}" icon="{userIcon}"/> {{/each}} </gmaps-map>`

<gt-geolocation> grabs the device's current location using the native geolocation API. It data-binds to the latitude and longitude of the location. <user-request> makes a request for all the current users, each of which have their own latitude and longitude, and data-binds the response of this request to users <gmaps-map> renders a Google Map with several nested components. It shows a marker for the current location using <gmaps-marker> . It shows a circle around the current location to display the accuracy of the geolocation using <gmaps-circle> For each user that came back in the <user-request> , it shows their location and individual icon The helper components for Markers, Circles, Polygons, and others wrap additional Google maps APIs.

This demonstrates some extremely powerful, expressive template logic all made possible with components and their data-bound properties. Entire maps and their data can be expressed, without writing a line of JavaScript.

3. bit-c3

<bit-c3> <bit-c3-data> {{#each dataColumns}} <bit-c3-data-column value="{.}" /> {{/each}} </bit-c3-data> </bit-c3>

The bit-c3 component is actually an free and open source chart component that wraps C3.js (a D3-based charting library).

The docs show plenty of examples and API documentation.

This again shows off the ease with which we can modify behavior as dynamic and complex as graphing, simply by removing or adding a nested component.

Component Design

To make applications entirely from custom HTML elements, we must design abstract, reusable components. This means each component must designed to be both simple and flexible.

1. Simple

Each component performs one function really well. Notice that the examples above are each an individual building block, and express just enough logic to make themselves useful.

2. Flexible

In order for a component to serve as reusable enough for many use cases, they often need to have optional properties and sane defaults that configure their behavior.

For example, the bit-wizard-step component has an optional skip attribute, which configures that step to be skipped in certain conditions:

<bit-wizard-step {{#and firstName lastName}}skip{{/and}}>

Each component's API was designed with optional properties to make them more flexible and reusable.

Wrapping up

We plan to continue exploring the use of component heavy architectures after seeing the major benefits within our applications, and we encourage you to try the same.