Two-way data binding with Web UI custom elements and models

Preface

This could very well be my last Web UI post! The Dart team announced they started on polymer.dart, a port of the Polymer project to Dart. Polymer.dart is the next evolution of Web UI. Luckily, the concepts are mostly the same and the syntax is mostly the same, because Web UI is also an implementation of the Web Components.

So why am I still writing about Web UI? Lots of people have asked about two-way data binding between a component and a model, so I wanted to have a record of this functionality. Also, what's described below isn't too different than how polymer.dart works (and we'll cover it in a future post).

I look forward to the glorious new world of coordinated polyfills and new custom elements. Go go polymer.dart and the Polymer project!

Intro

The Dart class that backs a custom element can itself also take part in two-way data binding. This post explores how to bind a model object to a web component property. If a model's field changes, the custom element's property changes. If the custom element's property changes, the model's field changes.


(Note: the same technique, with different syntax, should work in polymer.dart as well.)

The demo app contains:
  • A model for a Switch
  • A custom element for a Toggle
The goal

When the switch is flipped, we want to flip the toggle. When the toggle is flipped, we want to flip the switch. Both Switch and Toggle don't know about each other. Let's connect them!

Defining the Switch model

The Switch class is a standard business model, and it's marked as @observable.

 library toggle;  
   
 import 'package:web_ui/web_ui.dart';  
   
 @observable  
 class Switch {  
  String flipped = 'off';  
 }  

Defining the ToggleComponent

The Toggle custom element has two files: the <element> definition and the Dart class.

The Dart class for ToggleComponent is also @observable, and has a single field: state.

 library turn_off;  
   
 import 'package:web_ui/web_ui.dart';  
   
 @observable  
 class ToggleComponent extends WebComponent {  
  String state = 'On';  
    
  void toggle() {  
   if (state == 'On') {  
    state = 'Off';  
   } else {  
    state = 'On';  
   }  
  }  
 }  

The custom component renders a button, and displays the state field. The button changes the state field by calling the toggle() method.

 <!DOCTYPE html>  
   
 <html>  
  <body>  
   <element name="toggle" constructor="ToggleComponent" extends="div">  
    <template>  
     <button on-click="toggle()">Toggle</button>  
     <p>Inside the component: The toggle is {{state}}</p>  
    </template>  
    <script type="application/dart" src="toggle_component.dart"></script>  
   </element>  
  </body>  
 </html>  

Initializing and controlling the switch

Inside the main app, create a single Switch instance and two methods that turn the switch on or off.

 library app;  
   
 import 'dart:html';  
 import 'package:web_ui/web_ui.dart';  
 import 'switch.dart';  
   
 Switch lightSwitch = new Switch();  
   
 turnSwitchOn() {  
  lightSwitch.flipped = 'On';  
 }  
   
 turnSwitchOff() {  
  lightSwitch.flipped = 'Off';  
 }  
   
 void main() {  
 }  

Connecting the Switch to the Toggle

Web UI supports two-way binding on custom elements with its bind-foo attribute, where foo is the name of a property on the custom element's backing class.

In this example, we bind toggle's state to switch's flipped field. This code comes from app.html:

   <div is="toggle" bind-state="lightSwitch.flipped"></div>  
     
   <p>The switch is set to {{lightSwitch.flipped}}</p>  
     
   <p>  
    <button on-click="turnSwitchOn()">Turn Switch On</button>  
    <button on-click="turnSwitchOff()">Turn Switch Off</button>  
   </p>  

Live demo


Press the Toggle button (the custom component) and it runs toggle() which flips the ToggleComponent's internal state field. Because state is bound to lightSwitch.flipped, the switch is also flipped. Pretty cool!

Summary

You can bind a component's property to a model's field. This works today with Web UI's bind-foo attribute, and should work with polymer.dart's foo="{{model.field}}" attribute.

Meanwhile, Web UI is being phased out in favor of polymer.dart. Both libraries have the same goal: provide excellent implementations of modern and emerging web specifications and to help developers build amazing modular and beautiful web apps!

Popular posts from this blog

Lists and arrays in Dart

Converting Array to List in Scala

Null-aware operators in Dart