One-way data binding

Creating new elements is easy, but without any logic behind them, it isn't very practical. We've already said that polymer.dart utilizes the Observe library, which we can use to listen to changes of attributes on element instances and also to create one-way data bindings, which we'll use now.

Our new custom element will be called <one-way-book>, and we'll reuse the 3D books that we created in Chapter 4, Developing a Mobile App with Dart. Just to present polymer's capabilities, we'll add two control buttons that will rotate each book, and a caption of the book floating above them with the name of its current cover image.

The body for this example will look very familiar. We'll just add two elements with two custom attributes that we'll use later in Dart code:

<!-- web/index.html -->  
<body>
  <one-way-book cover-image="./dune-cover-new.jpg"
        base-color="#da944c"></one-way-book>
  <one-way-book cover-image="./dune-cover-old.jpg"
        base-color="#000"></one-way-book>
</body>

The HTML template is going to be slightly more complicated with a few new features:

<!-- lib/one-way-book.html –->
<link rel="import" href="../packages/polymer/polymer.html">
<polymer-element name="one-way-book">
  <template>
    <style>
     /* This refers to the host element. */
    :host { display: inline-block; position: relative;
            transform-style: preserve-3d; margin: 20px; }
    div { transform-style: preserve-3d; }
    div > div { width: 150px; height: 230px; position: absolute; }
     /* This is the same like in Chapter 4. */
    </style>

    <div>
      <p id="name">{{coverImage}}<br />{{rotateYString}}</p>
      <div class="front"
          style="background-image:url({{coverImage}})"></div>
      <div class="left"></div>
      <div class="right"></div>
      <div class="back"></div>
      <div class="top"></div>
      <div class="bottom"></div>
    </div>
    
    <p id="buttons">
       <!-- Buttons with event listeners. -->
      <button on-click="{{rotateLeft}}">&lt; left</button>
      <button on-click="{{rotateRight}}">right &gt;</button>
    </p>
  </template>
  <!-- Dart script that will control this custom element. -->
  <script type="application/dart"
      src="one-way-book.dart"></script>
</polymer-element>

As you can see, there are two unusual things:

  • {{coverImage}} and {{rotateYString}}: These are notations that render instance properties to this place in an HTML document. The associated property has to be marked with the @observe annotation. This is the one-way data binding that we mentioned earlier. Changes to the variable in the Dart code will automatically update the rendered value in HTML.
  • on-click: This is a declarative event mapping that is used to bind events right into the HTML. When the event is triggered, polymer.dart will call only the listener associated with this particular element's instance. There are more types of events, such as on-change (for input elements) or on-tap.

Note

Note that we're using {{coverImage}} in two different places—first as a text inside <p> tag and then right inside the style attribute of a div element, in order to avoid setting this style manually in the Dart code.

We'll start the Dart code with just a class stub:

// lib/one-way-book.dart
// Associate this class with the tag name.
// Each instance of <one-way-book> will be
// controlled by an instance of OneWayBookElement.
@CustomTag('one-way-book')
class OneWayBookElement extends PolymerElement {

  // This property can be embedded in the HTML by {{coverImage}}.
  @observable String coverImage;

  // Constructor is called every time a new instance is created.
  OneWayBookElement.created() : super.created() {
    // ...
  }

  attributeChanged(String name, String old, String newVal) {}
  rotateLeft(Event e, var detail, Node target) { }
  // ...
}

Our custom element extends PolymerElement, but it can extend any HTML element that implements Polymer and Observable abstract classes and provides the MyElement.created() constructor that calls super.created().

Elements in polymer.dart have five life cycle methods:

  • CustomElement.created(): This constructor is used when creating a new instance of this element
  • attached(): This instance is inserted into the DOM
  • detached(): This instance is removed from the DOM
  • ready(): This is called when the Shadow DOM is created, event listeners and properties are binded
  • attributeChanged(): This is called every time an attribute is changed, removed, or added to the element

This is the complete source code with comments:

// lib/one-way-book.dart
import 'dart:html';
import 'package:polymer/polymer.dart';

@CustomTag('one-way-book')
class OneWayBookElement extends PolymerElement {

  // These properties can be embedded in the HTML. 
  @observable String coverImage;
  @observable String rotateYString;
  int rotateY = 0;
  
  void updateBaseColor(String newColor) {
    // Btw, shadowRoot.host refers to the host element itself.
    shadowRoot.querySelector('.left').style.background = newColor;
    shadowRoot.querySelector('.back').style.background = newColor;
  }
  
  // Constructor called every time a new instance is created.
  OneWayBookElement.created() : super.created() {
    coverImage = this.attributes['cover-image'];
    updateBaseColor(this.attributes['base-color']);
  }
  
  // Listen to all attribute changes. 
  void attributeChanged(String name, String old, String newVal) {
    super.attributeChanged(name, oldValue, newValue);
    print('$name: $newValue (old $oldValue)'),
    // base-color attribute has been changed.
    if (name == 'base-color') {
      this.updateBaseColor(newValue);
    } else if (name == 'cover-image') {
      // Note that we don't need to change cover-image manually
      // since this property is embedded right in the HTML it'll
      // update automatically.
      coverImage = newValue;
    }
  }
  
  // Event listeners triggered on button clicks.
  void rotateLeft(Event e, var detail, Node target) {
    rotateY -= 10; 
    updateRotation();
  }

  void rotateRight(Event e, var detail, Node target) {
    rotateY += 10; 
    updateRotation();
  }

  void updateRotation() {
    rotateYString = 'rotateY(${rotateY}deg)';
    var elm = shadowRoot.querySelector('div'),
    elm.style.transform = rotateYString;
  }
}

The main.dart file is the same as the one in the previous example.

What happens in the browser is obvious. Clicking on one of the buttons calls their binded methods in their instance of OneWayBookElement, which changes CSS3 transformations in the rotateYString property. Each change to this variable updates all its bindings in the HTML.

One-way data binding

To test whether changing the element's attributes really triggers attributeChanged(), we can use Developer Tools right in Dartium. Just double-click on, for example, the base-color attribute to anything you want, and you can see that each change is immediately propagated to the element's instance in Dart. You can try changing the cover-image attribute as well.

Instead of the @observable annotation, we could use @published for coverImage that watches the property for changes (just like @observable) and also binds the element's attribute of this name with this property. This is nice when you don't need any additional logic when attribute values change.

Loops and conditions in templates

Polymer.dart comes with custom attributes if and repeat, which can evaluate simple logic useful mostly to hide and show DOM subtrees. We could use it, for example, to hide the book title with the rotation and image name when the rotation is larger than 30 degrees:

// lib/one-way-book.dart
// Declare rotateY in OneWayBookElement class.
@observable int rotateY = 0;

<!-- lib/one-way-book.html –->
<div>
  <template if="{{ rotateY < 30 && rotateY > -30 }}">
    <p id="name">{{ coverImage }}<br />{{ rotateYString }}</p>
  </template>
</div>

When the condition inside the if attribute evaluates to true, its inner subtree is placed in the parent element at the same location where the <template> tag is used.

Similarly, with the repeat attribute, we can iterate a collection. For example, if we had both book covers in a list called booksCovers, we could render them with:

<template repeat="{{ image in booksCovers }}">
  <one-way-book cover-image="{{ image }}" base-color="#000">
  </one-way-book>
</template>

Data binding applies to all expressions, so changing any variable will automatically cause reevaluation of the entire expression.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.245.233