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}}">< left</button> <button on-click="{{rotateRight}}">right ></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
.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 elementattached()
: This instance is inserted into the DOMdetached()
: This instance is removed from the DOMready()
: This is called when the Shadow DOM is created, event listeners and properties are bindedattributeChanged()
: This is called every time an attribute is changed, removed, or added to the elementThis 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.
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.
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.
3.133.133.61