Sunday, January 12, 2014

Day 994: Stay Classy with Polymer Models


There are two good ways to learn something new: flail around making dumb mistakes and reading smarter people's words and code. Naturally, I prefer the former.

In all seriousness, I find it easier to internalize how a language or tool wants me to work if I struggle to make it work the way I think it should. Usually I can learn a thing or two about my subject of study and I inevitably get exposed to smarter people's work while I figure out why my approach does not work. It might seems counter-intuitive, but really, making mistakes—the worse the better—really works well as I learn.

But tonight, I try the opposite. I have been working against Polymer long enough. It is time that I tried a trick of two that I have picked up along the way. I start with the “model pattern,” for lack of a better name. Actually a better name might simply be the strategy pattern because that is what it really is. But I keep calling it the model pattern when I come across others doing it in Polymer mostly because there is always a model attribute on the Polymer.

I don't know why, but I find that real classy. I want to be classy too. And what's more classy that order pizza? So I am going to build an <x-pizza> element that can build a pizza for order.

I start a new web application with a Dart Pub pubspec.yaml that pulls in Polymer.dart:
name: model_example
dependencies:
  polymer: any
dev_dependencies:
  unittest: any
transformers:
- polymer:
    entry_points: web/index.html
I start a pizza building homepage to smoke test my model pattern Polymer. I create it as web/index.html:
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Pizza Maker</title>
    <!-- Load component(s) -->
    <link rel="import" href="packages/model_example/elements/x-pizza.html">
    <!-- Load Polymer -->
    <script type="application/dart">
      export 'package:polymer/init.dart';
    </script>
  </head>
  <body>
    <div class="container">
      <h1>Dart Bros. Pizza Builder</h1>
      <x-pizza></x-pizza>
    </div>
  </body>
</html>
That is almost boilerplate at this point. I pull in the Polymer definition and the Polymer.dart platform at top. Then I use the <x-pizza> element below.

Similarly, the <x-pizza> definition is fairly straight-forward. I create lib/elements/x-pizza.html as:
<polymer-element name="x-pizza">
  <template>
    <h2>Build Your Pizza</h2>
    <pre>{{pizzaState}}</pre>
    <p>
      <select class="form-control" value="{{currentFirstHalf}}">
        <option>Choose an ingredient...</option>
        <option value="{{ingredient}}" template repeat="{{ingredient in ingredients}}">
          {{ingredient}}
        </option>
      </select>
      <button on-click="{{addFirstHalf}}" type="button" class="btn btn-default">
        Add First Half Topping
      </button>
    </p>
    <!-- ... -->
  </template>
  <script type="application/dart" src="x_pizza.dart"></script>
</polymer-element>
That is just variable binding in templates.

The bulk of the work comes in the backing class, which needs a Pizza class:
class Pizza {
  List<String> firstHalfToppings = [];
  List<String> secondHalfToppings = [];
  List<String> wholeToppings = [];
  toString()=> """First Half: $firstHalfToppings
Second Half: $secondHalfToppings
Whole: $wholeToppings""";
}
To use that as a model, I need only define an instance variable cleverly named “model” and assign an instance of Pizza to it:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  final bool applyAuthorStyles = true;
  final List ingredients = [
    'pepperoni',
    'sausage',
    'green peppers'
  ];

  @observable String currentFirstHalf = 'Choose an ingredient...';
  // ...
  @observable String pizzaState;

  Pizza model = new Pizza();

  XPizza.created(): super.created();
}
All that is needed at this point is to use the model, which I do in the various add-toppings bound methods:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  // ...
  addFirstHalf() {
    model.firstHalfToppings.add(currentFirstHalf);
    pizzaState = model.toString();
  }
  // ...
}
And that does the trick:



That is not quite the end of the story, however. I should be able to make the model itself observable so that, when its internal state is updated, it can trigger the update to the value displayed to the hungry buyer. I will leave that for another day. For now, I am happy just being on the way to classiness.


Day #994

2 comments: