Monday, January 13, 2014

Day 995: Observing Model Attributes in Polymer


As of last night, I have a nifty little pizza builder Polymer:



OK, so maybe not that nifty, but it was pretty easy to slap together. It is also fairly well-factored, slapped-together code, by virtue of a separate model that describes the internal state of the pizza. There is still room for improvement, however, as I would like for the model to be able to signal to the Polymer (or anything else for that matter) that its internal state has changed.

Currently, when the user adds an ingredient to the pizza, each of the various add-ingredient methods need to tell the main UI element to re-draw itself:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  // ...
  addFirstHalf() {
    model.firstHalfToppings.add(currentFirstHalf);
    pizzaState = model.toString();
  }
  // ...
}
That's not horrible except each one of the add-ingredient methods needs to update the value of pizzaState in exactly the same way so that it is available in the template:
<polymer-element name="x-pizza">
  <template>
    <h2>Build Your Pizza</h2>
    <pre>{{pizzaState}}</pre>
    <!-- ... -->
  </template>
  <script type="application/dart" src="x_pizza.dart"></script>
</polymer-element>
But there must be a better way to communicate that information, right?

The approach that I take is making the various properties in the Pizza class @observable:
class Pizza {
  @observable List<String> firstHalfToppings = toObservable([]);
  // ...
}
Back in the main Polymer element, I mark the Pizza model as @observable as well:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  @published Pizza model;
  // ...
  addFirstHalf() {
    model.firstHalfToppings.add(currentFirstHalf);
  }
  // ...
}
I also remove the duplicated pizzaState setting.

To get this to work, I have to listen to the model's attribute:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  // ...
  @published Pizza model;
  XPizza.created(): super.created() {
    model.firstHalfToppings.changes.listen((records) {
      pizzaState = model.toString();
      print('changed: $records');
      print(model.toString());
    });
  }
  // ...
}
Listening to the model itself is not sufficient as it does not see changes from its own properties.

Interestingly, or should I say frustratingly, I cannot bind template variables to the model's properties:
<polymer-element name="x-pizza">
  <template>
    <h2>Build Your Pizza</h2>
    <pre>
{{model.firstHalfToppings}}
{{model.secondHalfToppings}}</pre>
  </template>
  <script type="application/dart" src="x_pizza.dart"></script>
</polymer-element>
They show the initial value of an empty list, but never update. What is especially frustrating is that, if I include pizzaState in addition to those bound model attributes, then it works:
<polymer-element name="x-pizza">
  <template>
    <h2>Build Your Pizza</h2>
    <pre>
{{pizzaState}}
{{model.firstHalfToppings}}
{{model.secondHalfToppings}}</pre>
  </template>
  <script type="application/dart" src="x_pizza.dart"></script>
</polymer-element>
The simple inclusion of that Polymer attribute results in both it being updated as well as the model attributes:



And I am not sure why. I think it may be time to take a break to get more familiar with model driven views in JavaScript tomorrow. After that, hopefully, I will have a better idea how to do this in Dart.


Day #995

No comments:

Post a Comment