Shailen Tuli's blog

Thoughts on coding in Dart, Ruby, Python and Javascript

A First App With Web Components Using Dart and Web UI Library

| Comments

Its time to finally create a simple hello world app using web components and the dart Web UI library.

There is already a ton of literature out there on why web components are a tremendously good idea and I won’t try to do a huge ‘sell’ here. If you are completely new to web components, I can recommend this really good introductory blog post by Seth Ladd

Or, if you are impatient, here’s an (almost) tweet sized summary: web components allow developers to encapsulate their UI elements as easily reusable components. You can define templates with markup that is inert until activated later, apply decorators to enhance the look of those templates, create custom elements and play with the shadow DOM. In this little app, we will not be tikering with decorators or the shadow DOM; we will be creating templates and defining our own custom element.

The app is called bookstore and you can find the code at https://github.com/shailen/bookstore.

Since I am new to web components and the Dart Web UI library, I am going to keep this simple. In its current iteration, the app will show the user the list of books in the bookstore and let the user add books to the collection. The plan is to start with something minimal and over the next few weeks and months build something a little bit elaborate (add Authors, Publishers, more attributes to our Book class, reviews, etc) while preserving the one-page feel of the app.

Important Files

There are a few important files in bookstore’s web directory that are worth discussing now:

lib/models.dart contains code for a Book class

web/books.html contains the basic markup for the app

web/books.dart contains the Dart code that goes with that markup

web/book_component.html contains the markup for our web component

web/book_component.dart contains the Dart code for our web component

build.dart helps use compile our code so that it can be run

We’ll discuss each of these files in detail soon.

lib/models.dart

We’re going to be creating books. This file defines a simple Book class. Our books only have 1 attribute for now, a title (I told you this was simple ;).

library models;

class Book {
  String title;
  Book(this.title);
}

web/books.html

Here is the entirety of teh web/books.html file. Consider this an entry point into the app:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Books</title>
    <link rel="components" href="book_component.html">
  </head>
  <body>
    <h1>Books</h1>

    <input id="new-book" type="text" placeholder="add another title">
    <button on-click="createNewBook()">Add Book</button>

    <ul id="books">
      <template iterate="book in books">
        <x-book-item book="{{ book }}"></x-book-item>
      </template>
    </ul>

    <!-- this is the non web-component way to create the <li>s
    <ul>
      <template iterate='book in books'>
        <li>{{ book.title }}</li>
      </template>
    </ul>
    -->

    <script type="application/dart" src="books.dart"></script>
    <script type="text/javascript" src="https://dart.googlecode.com/svn/branches/bleeding_edge/dart/client/dart.js"></script>

  </body>
</html>

A few things to notice here:

<link rel="components" href="book_component.html">

is the way you access the contents of our web component from this file.

We create an input where the user enters the name of a book and an accompanying button:

<input id="new-book" type="text" placeholder="add another title">
<button on-click="createNewBook()">Add Book</button>

Notice the on-click? That is the way we inline an event listener: when the button is clicked, createNewBook() fires (we’ll get to that function soon)

And finally the code that actually deals with the web component:

<ul id="books">
  <template iterate="book in books">
    <x-book-item book="{{ book }}"></x-book-item>
  </template>
</ul>

A few things to note here. We define a <template> tag; we loop over our collection of books (stored in a variable books in web/books.dart); we instantiate our web component (<x-book-item></x-book-item>) and we pass each book in the loop as a template variable when we instantiate the web component.

There’s a lot going on here. Make sure you understand the above paragraph!

web/books.dart

web/books.html has a link to a Dart file at the bottom:

<script type="application/dart" src="books.dart"></script>

And here is what books.dart looks like:

import 'dart:html';
import 'package:bookstore/models.dart';

List<Book> books = [];

// binding created auto-magically!
void createNewBook() {
  var input = query("#new-book");
  books.add(new Book(input.value));
  input.value = "";
}

main() {
  // create some data so the page doesn't look empty
  ["War and Peace", "The Idiot", "Crime and Punishment"].forEach((title) {
    books.add(new Book(title));
  });
} 

It is pretty straightforward stuff: we import models.dart, the file that contains the Book class; we create a books variable to store our collection. We define createNewBook() to add a book to books, and we define a main() function.

This Dart file MUST contain a main(), even an empty one will do. In our case, we will add a few books to our books collection, so that there is some data to display.

web/book_component.html

This contains the code that defines our web component:

<!DOCTYPE html>
<html>
  <body>
    <element name="x-book-item" constructor="BookComponent" extends="li">
      <template> 
        <li>{{ book.title }}</li>
      </template>

      <script type="application/dart" src="book_component.dart"></script>
    </element>
  </body>
</html>

A few things to understand here: we create a custom <element>; we give it a name, x-book-item (all element names must begin with an x-); we call a constructor, BookComponent (defined in web/book_componenet.dart, we’ll get to that file shortly) and we declare that our custom element extends and li.

Inside our <element>, we create a <template> that stores the <li> that contains a book’s title.

And finally, we link to the accompanying Dart file, book_component.dart.

web/book_component.dart

Here we get to define our BookComponent class (remember that we declared that the <element> we created in book_componenet.html use this constructor?):

import 'package:web_ui/web_ui.dart';
import 'package:bookstore/models.dart';

class BookComponent extends WebComponent {
  Book book;
}

BookComponent extends WebComponent (this is the only option for subclassing at the moment; that may change in the future) and contains a book attribute (this can be set using the book = syntax when the web component is initialized). That’s it.

build.dart

import 'package:web_ui/component_build.dart';
import 'dart:io';

void main() {
  build(new Options().arguments, ['web/books.html']);
}

To actually build the project, run build.dart (this will create an out directory); then run web/out/books.html.

pubspec.yaml

The app only has a single pub dependency, web_ui:

name: bookstore
description: A sample app to demonstrate the use of a web component

dependencies:
    web_ui

Summary

This is a fair bit of code for a simple hello-world caliber app. Is all this web component stuff really necessary, or is it overkill?

We’re just starting out, so this may seem like too much of a production given what the needs of our app. But we have already established a pretty important development principle: our UI elements can be nicely ENCAPSULATED (!) and then used as necessary. We have taken the first baby steps towards creating a widget that displays the content of each book. As our application grows in complexity, our ‘widget’ will become more elaborate and we will want to use it in all sorts of different contexts in our app. A composable, encapsulated UI component - a web component - that can be instantiated with varying arguments will then prove to be quite useful.

Comments