Wednesday, January 15, 2014

Day 997: Cascade Observers in Polymer (Dart)


I am usually pretty safe assuming that my assumptions are wrong. Unless they're not. Happily, this turned out to be the case with some code I was trying to force in Polymer.dart. Thanks to a reimplementation in the JavaScript version of the Polymer project, I now have a better understanding of bound variables in Polymer.

The problem arose when I tried to bind some Pizza model attributes in a <x-pizza> element:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="x-pizza">
  <template>
    <h2>Build Your Pizza</h2>
    <pre>
{{model.firstHalfToppings}}
{{model.secondHalfToppings}}</pre>
    <!-- ... -->
  </template>
  <script src="x_pizza.dart"></script>
</polymer-element>
Those attributes were not bound, at least not such that their changes were observed in the template. Interestingly, when an actual bound attribute, say a “pizza state” instance variable, was bound in the same <pre> element in my template, then the model attributes were updated:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="x-pizza">
  <template>
    <h2>Build Your Pizza</h2>
    <pre>
{{pizzaState}}
{{model.firstHalfToppings}}
{{model.secondHalfToppings}}</pre>
    <!-- ... -->
  </template>
  <script src="x_pizza.js"></script>
</polymer-element>
In fact, the model attributes in the template were updated whenever the pizzaState instance variable was updated. So it seems that including a truly bound variable inside a tag will force all bound variables—model or element—to update. That seems useful information to file away for another day.

For today, I would like to see how the JavaScript library's solution of path expressions to watch for changes in a model translates into Dart. From last night's JavaScript, the solution was to observe three list attributes in the model:
Polymer('x-pizza', {
  observe: {
    'model.firstHalfToppings': 'updatePizzaState',
    'model.secondHalfToppings': 'updatePizzaState',
    'model.wholeToppings': 'updatePizzaState'
  },
  // ...
});
The observe block tells Polymer to call the updatePizzaState() method (which updates pizzaState for the template) whenever a change is seen in any of those lists.

If there is an equivalent for the observe block in Polymer.dart, I cannot find it.

What I wind up using instead is simple change listeners. For this to work, the model in my Model Driven View, Pizza, needs to be “observable.” Furthermore, since the properties in question are lists, I need a specialized “observable” instance of those lists. Both of these are established with judicious used of the @observable annotation:
@observable
class Pizza {
  List<String> firstHalfToppings = toObservable([]);
  List<String> secondHalfToppings = toObservable([]);
  List<String> wholeToppings = toObservable([]);
}
With that, I can create something that looks like an observe block. If you squint:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  // ...
  Pizza model;

  XPizza.created(): super.created() {
    model = new Pizza()
      ..firstHalfToppings.changes.listen(updatePizzaState)
      ..secondHalfToppings.changes.listen(updatePizzaState)
      ..wholeToppings.changes.listen(updatePizzaState);
    // ...
  }
 // ...
}
The double dot operator in Dart is a method cascade. It returns the original object instead of the return value of the method (or property lookup). Above, this allows me to ask for the firstHalfToppings, secondHalfToppings, and wholeToppings properties from the newly created Pizza object. For each of these, I listen to the changes stream for a change and update the “pizza state” accordingly.

And that does the trick:



I can live with that method cascade solution. At least for tonight. It is a solution (and a relatively clean one at that), but I am not 100% certain that it is the solution for this in Polymer.dart. While digging through class documentation, I came across Polymer's bind() and bindProperty() methods. Both use similar path expressions as those found in the JavaScript observe block. I am not entirely sure that they operate in the same problem space, but that is a topic for another day. Like tomorrow!


Day #997

No comments:

Post a Comment